diff options
25 files changed, 2110 insertions, 558 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e47d0..7c65f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.8.9-0.11.0] - unreleased +### Changed +- Completely re-done the config gui (`/moo config`) + - now separated into sections and sub-sections + - added moar configurable things + - some config settings have a live-preview next to them +- Improved SkyBlock dungeon party finder + - more config options + - marks (non-)joinable parties even better than before +- Improved SkyBlock dungeon performance overlay + - Overlay can be moved more precisely + - Dungeons can be "joined" and "left" manually (if the automatic detection fails): `/moo dungeon <enter/leave>` +- Improved handling of invalid/missing Hypixel API key + ## [1.8.9-0.10.2] - 15.09.2020 ### Added - Added keybinding (default `M`) to open chat with `/moo ` pre-typed @@ -195,6 +209,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). *Note:* The 'best friends' list is currently available via <kbd>ESC</kbd> > Mod Options > Cowlection > Config > bestFriends. +[1.8.9-0.11.0]: https://github.com/cow-mc/Cowlection/compare/v1.8.9-0.10.2...master [1.8.9-0.10.2]: https://github.com/cow-mc/Cowlection/compare/v1.8.9-0.10.1...v1.8.9-0.10.2 [1.8.9-0.10.1]: https://github.com/cow-mc/Cowlection/compare/v1.8.9-0.10.0...v1.8.9-0.10.1 [1.8.9-0.10.0]: https://github.com/cow-mc/Cowlection/compare/v1.8.9-0.9.0...v1.8.9-0.10.0 @@ -6,13 +6,14 @@ It is a collection of different features mainly focused on Hypixel SkyBlock. đ ## Current features â Use `/moo help` to see all available commands. +â Use `/moo config` to adjust the features to your needs. | Feature | Command/Usage | |-------------------------------------------------------------------------|-----------------------------------------| | 'Best friends' list to limit the amount of join and leave notifications (always up-to-date names even after player name changes). Also checks best friends' online status automatically | `/moo add/remove/list/online` | | Search through your Minecraft log files (click the `?` for more info) | `/moo search` | | Stalk a player (check online status, current game, ...) | `/moo stalk` | -| Toggle join/leave notifications for friends, guild members or best friends separately | `/moo toggle` | +| Toggle join/leave notifications for friends, guild members or best friends separately | `/moo config` → Notifications | | 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> | | Tab-completable usernames for several commands (e.g. `/party`, `/invite`, ...) | `/moo config` → `Commands with Tab-completable usernames` for full list of commands | @@ -25,8 +26,8 @@ It is a collection of different features mainly focused on Hypixel SkyBlock. đ |-------------------------------------------------------------------------|-----------------------------------------| | Stalk SkyBlock stats of a player | `/moo stalkskyblock` | | Analyze minions on a private island | `/moo analyzeIsland` | -| Dungeon interfaces enhancements (normalize dungeon item stats, improved party finder) | Hold <kbd>shift</kbd> while viewing a dungeon item tooltip | -| Dungeon performance tracker: Skill score calculation, class milestone tracker, destroyed crypts tracker, and elapsed time indicator | automatically; or with `/moo dungeon`; Overlay can be edited with `/moo dungeonGui` | +| Dungeon interfaces enhancements (normalize dungeon item stats, improved party finder) | Hold <kbd>shift</kbd> (configurable) while viewing a dungeon item tooltip | +| Dungeon performance tracker and overlay: Skill score calculation, class milestone tracker, destroyed crypts tracker, and elapsed time indicator | automatically; or with `/moo dungeon` | ## Download You can download the compiled .jar files from the [release section](https://github.com/cow-mc/Cowlection/releases). diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java index 50df539..4aefd5b 100644 --- a/src/main/java/de/cowtipper/cowlection/Cowlection.java +++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java @@ -29,7 +29,6 @@ import java.io.File; @Mod(modid = Cowlection.MODID, name = Cowlection.MODNAME, version = Cowlection.VERSION, clientSideOnly = true, - guiFactory = "@PACKAGE@.config.MooGuiFactory", updateJSON = "https://raw.githubusercontent.com/cow-mc/Cowlection/master/update.json") public class Cowlection { public static final String MODID = "@MODID@"; @@ -54,15 +53,16 @@ public class Cowlection { logger = e.getModLog(); modsDir = e.getSourceFile().getParentFile(); + chatHelper = new ChatHelper(); + this.configDir = new File(e.getModConfigurationDirectory(), MODID + File.separatorChar); if (!configDir.exists()) { configDir.mkdirs(); } friendsHandler = new FriendsHandler(this, new File(configDir, "friends.json")); - config = new MooConfig(this, new Configuration(new File(configDir, MODID + ".cfg"))); - - chatHelper = new ChatHelper(); + moo = new CredentialStorage(new Configuration(new File(configDir, "do-not-share-me-with-other-players.cfg"))); + config = new MooConfig(this, new Configuration(new File(configDir, MODID + ".cfg"), "1")); } @EventHandler @@ -77,7 +77,7 @@ public class Cowlection { } // key bindings keyBindings = new KeyBinding[1]; - keyBindings[0] = new KeyBinding("key.cowlection.moo.desc", Keyboard.KEY_M, "key.cowlection.category"); + keyBindings[0] = new KeyBinding("key.cowlection.moo", Keyboard.KEY_M, "key.cowlection.category"); for (KeyBinding keyBinding : keyBindings) { ClientRegistry.registerKeyBinding(keyBinding); diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java index 6be1a6c..881a446 100644 --- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java +++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java @@ -5,9 +5,9 @@ import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.command.exception.ApiContactException; import de.cowtipper.cowlection.command.exception.InvalidPlayerNameException; import de.cowtipper.cowlection.command.exception.MooCommandException; -import de.cowtipper.cowlection.config.DungeonOverlayGuiConfig; +import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.config.MooConfig; -import de.cowtipper.cowlection.config.MooGuiConfig; +import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.data.*; import de.cowtipper.cowlection.data.HySkyBlockStats.Profile.Pet; import de.cowtipper.cowlection.handler.DungeonCache; @@ -15,6 +15,7 @@ import de.cowtipper.cowlection.search.GuiSearch; import de.cowtipper.cowlection.util.*; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.settings.GameSettings; import net.minecraft.command.*; import net.minecraft.entity.Entity; import net.minecraft.entity.item.EntityArmorStand; @@ -79,12 +80,7 @@ public class MooCommand extends CommandBase { } else if (args[0].equalsIgnoreCase("list")) { handleListBestFriends(); } else if (args[0].equalsIgnoreCase("online")) { - if (main.getFriendsHandler().getBestFriends().size() > 0) { - main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Checking online status of " + EnumChatFormatting.WHITE + main.getFriendsHandler().getBestFriends().size() + EnumChatFormatting.GRAY + " best friends. This may take a few seconds."); - main.getFriendsHandler().runBestFriendsOnlineCheck(true); - } else { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "You haven't added anyone to your best friends list yet. Do so with " + EnumChatFormatting.WHITE + "/moo add <playerName>"); - } + handleBestFriendsOnlineCheck(); } else if (args[0].equalsIgnoreCase("nameChangeCheck")) { handleNameChangeCheck(args); } @@ -100,14 +96,11 @@ public class MooCommand extends CommandBase { handleAnalyzeIsland(sender); } else if (args[0].equalsIgnoreCase("dungeon") || args[0].equalsIgnoreCase("dung")) { handleDungeon(args); - } else if (args[0].equalsIgnoreCase("dungeonGui") || args[0].equalsIgnoreCase("guiDungeon") - || args[0].equalsIgnoreCase("guiDung") || args[0].equalsIgnoreCase("dungGui")) { - displayGuiScreen(new DungeonOverlayGuiConfig(main)); } //endregion //region sub-commands: miscellaneous - else if (args[0].equalsIgnoreCase("config") || args[0].equalsIgnoreCase("toggle")) { - displayGuiScreen(new MooGuiConfig(null)); + else if (args[0].equalsIgnoreCase("config")) { + displayGuiScreen(new MooConfigGui()); } else if (args[0].equalsIgnoreCase("search")) { displayGuiScreen(new GuiSearch(main.getConfigDirectory(), CommandBase.buildString(args, 1))); } else if (args[0].equalsIgnoreCase("guiscale")) { @@ -278,6 +271,18 @@ public class MooCommand extends CommandBase { : EnumChatFormatting.DARK_GREEN + String.join(EnumChatFormatting.GREEN + ", " + EnumChatFormatting.DARK_GREEN, bestFriends))); } + private void handleBestFriendsOnlineCheck() throws MooCommandException { + if (!CredentialStorage.isMooValid) { + throw new MooCommandException("You haven't set your Hypixel API key yet or the API key is invalid. Use " + EnumChatFormatting.DARK_RED + "/api new" + EnumChatFormatting.RED + " to request a new API key from Hypixel or use " + EnumChatFormatting.DARK_RED + "/" + this.getCommandName() + " apikey <key>" + EnumChatFormatting.RED + " to manually set your existing API key."); + } + if (main.getFriendsHandler().getBestFriends().size() > 0) { + main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Checking online status of " + EnumChatFormatting.WHITE + main.getFriendsHandler().getBestFriends().size() + EnumChatFormatting.GRAY + " best friends. This may take a few seconds."); + main.getFriendsHandler().runBestFriendsOnlineCheck(true); + } else { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "You haven't added anyone to your best friends list yet. Do so with " + EnumChatFormatting.WHITE + "/moo add <playerName>"); + } + } + private void handleNameChangeCheck(String[] args) throws CommandException { if (args.length != 2) { throw new WrongUsageException("/" + getCommandName() + " nameChangeCheck <playerName>"); @@ -562,20 +567,24 @@ public class MooCommand extends CommandBase { private void handleDungeon(String[] args) throws MooCommandException { DungeonCache dungeonCache = main.getDungeonCache(); - if (args.length == 2 && args[1].equalsIgnoreCase("gui")) { - // edit dungeon gui - displayGuiScreen(new DungeonOverlayGuiConfig(main)); + if (args.length == 2 && args[1].equalsIgnoreCase("enter")) { + // enter dungeon in case for some reason it wasn't detected automatically + dungeonCache.onDungeonEnterOrLeave(true); + } else if (args.length == 2 && args[1].equalsIgnoreCase("leave")) { + // leave dungeon in case for some reason it wasn't detected automatically + dungeonCache.onDungeonEnterOrLeave(false); } else if (dungeonCache.isInDungeon()) { dungeonCache.sendDungeonPerformance(); } else { - throw new MooCommandException(EnumChatFormatting.DARK_RED + "Looks like you're not in a dungeon... However, you can edit the Dungeon Performance overlay with " + EnumChatFormatting.RED + "/" + getCommandName() + " dungeon gui"); + throw new MooCommandException(EnumChatFormatting.DARK_RED + "Looks like you're not in a dungeon... However, you can manually enable the Dungeon Performance overlay with " + EnumChatFormatting.RED + "/" + getCommandName() + " dungeon enter" + EnumChatFormatting.DARK_RED + ". You can also force-leave a dungeon with " + EnumChatFormatting.RED + "/" + getCommandName() + " leave"); } } //endregion //region sub-commands: miscellaneous private void handleGuiScale(String[] args) throws CommandException { - int currentGuiScale = (Minecraft.getMinecraft()).gameSettings.guiScale; + GameSettings gameSettings = Minecraft.getMinecraft().gameSettings; + int currentGuiScale = gameSettings.guiScale; if (args.length == 1) { main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u279C Current GUI scale: " + EnumChatFormatting.DARK_GREEN + currentGuiScale); } else { @@ -583,7 +592,8 @@ public class MooCommand extends CommandBase { if (scale == -1 || scale > 10) { throw new NumberInvalidException(EnumChatFormatting.DARK_RED + args[1] + EnumChatFormatting.RED + " is an invalid GUI scale value. Valid values are integers below 10"); } - Minecraft.getMinecraft().gameSettings.guiScale = scale; + gameSettings.guiScale = scale; + gameSettings.saveOptions(); main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u2714 New GUI scale: " + EnumChatFormatting.DARK_GREEN + scale + EnumChatFormatting.GREEN + " (previous: " + EnumChatFormatting.DARK_GREEN + currentGuiScale + EnumChatFormatting.GREEN + ")"); } } @@ -598,7 +608,7 @@ public class MooCommand extends CommandBase { color = EnumChatFormatting.GREEN; colorSecondary = EnumChatFormatting.DARK_GREEN; } else { - firstSentence = "You haven't set your Hypixel API key yet."; + firstSentence = "You haven't set your Hypixel API key yet or the API key is invalid."; color = EnumChatFormatting.RED; colorSecondary = EnumChatFormatting.DARK_RED; } @@ -659,21 +669,20 @@ public class MooCommand extends CommandBase { private void sendCommandUsage(ICommandSender sender) { IChatComponent usage = new MooChatComponent("\u279C " + Cowlection.MODNAME + " commands:").gold().bold() + .appendSibling(createCmdHelpEntry("config", "Open mod's configuration")) + .appendSibling(new MooChatComponent("\n").reset().gray().appendText(EnumChatFormatting.DARK_GREEN + " â˘" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + " Commands marked with §d§lâˇ" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + " require a valid API key")) .appendSibling(createCmdHelpSection(1, "Best friends, friends & other players")) - .appendSibling(createCmdHelpEntry("stalk", "Get info of player's status")) + .appendSibling(createCmdHelpEntry("stalk", "Get info of player's status §d§lâˇ")) .appendSibling(createCmdHelpEntry("add", "Add best friends")) .appendSibling(createCmdHelpEntry("remove", "Remove best friends")) .appendSibling(createCmdHelpEntry("list", "View list of best friends")) - .appendSibling(createCmdHelpEntry("online", "View list of best friends that are currently online")) + .appendSibling(createCmdHelpEntry("online", "View list of best friends that are currently online §d§lâˇ")) .appendSibling(createCmdHelpEntry("nameChangeCheck", "Force a scan for a changed name of a best friend (is done automatically as well)")) - .appendSibling(createCmdHelpEntry("toggle", "Toggle join/leave notifications")) .appendSibling(createCmdHelpSection(2, "SkyBlock")) - .appendSibling(createCmdHelpEntry("stalkskyblock", "Get info of player's SkyBlock stats")) + .appendSibling(createCmdHelpEntry("stalkskyblock", "Get info of player's SkyBlock stats §d§lâˇ")) .appendSibling(createCmdHelpEntry("analyzeIsland", "Analyze a SkyBlock private island")) .appendSibling(createCmdHelpEntry("dungeon", "SkyBlock Dungeons: display current dungeon performance")) - .appendSibling(createCmdHelpEntry("dungeonGui", "SkyBlock Dungeons: edit dungeon performance GUI")) .appendSibling(createCmdHelpSection(3, "Miscellaneous")) - .appendSibling(createCmdHelpEntry("config", "Open mod's configuration")) .appendSibling(createCmdHelpEntry("search", "Open Minecraft log search")) .appendSibling(createCmdHelpEntry("guiScale", "Change GUI scale")) .appendSibling(createCmdHelpEntry("rr", "Alias for /r without auto-replacement to /msg")) @@ -706,8 +715,8 @@ public class MooCommand extends CommandBase { public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { if (args.length == 1) { return getListOfStringsMatchingLastWord(args, - /* Best friends, friends & other players */ "stalk", "add", "remove", "list", "online", "nameChangeCheck", "toggle", - /* SkyBlock */ "stalkskyblock", "skyblockstalk", "analyzeIsland", "dungeon", "dungeonGui", "guiDungeon", + /* Best friends, friends & other players */ "stalk", "add", "remove", "list", "online", "nameChangeCheck", + /* SkyBlock */ "stalkskyblock", "skyblockstalk", "analyzeIsland", "dungeon", /* miscellaneous */ "config", "search", "guiscale", "rr", "shrug", "apikey", /* update mod */ "update", "updateHelp", "version", "directory", /* help */ "help", diff --git a/src/main/java/de/cowtipper/cowlection/command/exception/ApiContactException.java b/src/main/java/de/cowtipper/cowlection/command/exception/ApiContactException.java index 09a04a0..f601e3e 100644 --- a/src/main/java/de/cowtipper/cowlection/command/exception/ApiContactException.java +++ b/src/main/java/de/cowtipper/cowlection/command/exception/ApiContactException.java @@ -1,7 +1,12 @@ package de.cowtipper.cowlection.command.exception; +import de.cowtipper.cowlection.Cowlection; + public class ApiContactException extends MooCommandException { public ApiContactException(String api, String failedAction) { super("Sorry, couldn't contact the " + api + " API and thus " + failedAction); + if (api.equals("Hypixel") && failedAction.contains("Invalid API key")) { + Cowlection.getInstance().getMoo().setMooValidity(false); + } } } diff --git a/src/main/java/de/cowtipper/cowlection/config/DungeonOverlayGuiConfig.java b/src/main/java/de/cowtipper/cowlection/config/DungeonOverlayGuiConfig.java deleted file mode 100644 index 18478f5..0000000 --- a/src/main/java/de/cowtipper/cowlection/config/DungeonOverlayGuiConfig.java +++ /dev/null @@ -1,129 +0,0 @@ -package de.cowtipper.cowlection.config; - -import de.cowtipper.cowlection.Cowlection; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.util.EnumChatFormatting; -import net.minecraftforge.fml.client.config.GuiButtonExt; -import net.minecraftforge.fml.client.config.GuiCheckBox; -import net.minecraftforge.fml.client.config.GuiSlider; -import org.apache.commons.lang3.StringUtils; -import org.lwjgl.input.Keyboard; - -import java.io.IOException; - -public class DungeonOverlayGuiConfig extends GuiScreen { - private final Cowlection main; - private GuiCheckBox checkBoxShowOverlay; - private GuiSlider sliderX; - private GuiSlider sliderY; - private GuiSlider sliderGuiScale; - private GuiButtonExt buttonCancel; - private GuiButtonExt buttonSave; - private final boolean wasDungOverlayEnabled; - private final int previousPositionX; - private final int previousPositionY; - private final int previousGuiScale; - - public DungeonOverlayGuiConfig(Cowlection main) { - this.main = main; - wasDungOverlayEnabled = MooConfig.dungOverlayEnabled; - previousPositionX = MooConfig.dungOverlayPositionX; - previousPositionY = MooConfig.dungOverlayPositionY; - previousGuiScale = MooConfig.dungOverlayGuiScale; - } - - @Override - public void initGui() { - int maxX = this.width - fontRendererObj.getStringWidth(StringUtils.repeat('#', 15)); - int maxY = this.height - fontRendererObj.FONT_HEIGHT * 5; // 5 = max lines output - int startX = Math.min(maxX, this.previousPositionX); - int startY = Math.min(maxY, this.previousPositionY); - - this.buttonList.add(this.checkBoxShowOverlay = new GuiCheckBox(30, this.width / 2 - 50, this.height / 2 - 30, " Show overlay", MooConfig.dungOverlayEnabled)); - this.buttonList.add(sliderX = new GuiSlider(20, this.width / 2 - 150, this.height / 2 - 12, 300, 20, "x = ", "", 0, maxX, startX, false, true)); - this.buttonList.add(sliderY = new GuiSlider(21, this.width / 2 - 150, this.height / 2 + 12, 300, 20, "y = ", "", 0, maxY, startY, false, true)); - this.buttonList.add(sliderGuiScale = new GuiSlider(22, this.width / 2 - 100, this.height / 2 + 37, 200, 20, "GUI scale: ", "%", 50, 200, MooConfig.dungOverlayGuiScale, false, true)); - this.buttonList.add(this.buttonCancel = new GuiButtonExt(31, this.width / 2 - 150, this.height / 2 + 65, 80, 20, EnumChatFormatting.RED + "Cancel")); - this.buttonList.add(this.buttonSave = new GuiButtonExt(32, this.width / 2 + 70, this.height / 2 + 65, 80, 20, EnumChatFormatting.GREEN + "Save")); - if (!MooConfig.dungOverlayEnabled) { - sliderX.enabled = false; - sliderY.enabled = false; - sliderGuiScale.enabled = false; - } - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - // draw background - int padding = 20; - this.drawGradientRect(this.width / 2 - 150 - padding, this.height / 2 - 40 - fontRendererObj.FONT_HEIGHT - padding, - this.width / 2 + 150 + padding, this.height / 2 + 65 + 20 + fontRendererObj.FONT_HEIGHT + padding, - -1072689136, -804253680); - - // draw gui elements - String title = "Dungeon Performance Overlay Settings"; - this.drawCenteredString(this.fontRendererObj, EnumChatFormatting.BOLD + title, this.width / 2, this.height / 2 - 40 - fontRendererObj.FONT_HEIGHT, 0x00ffffff); - - GlStateManager.pushMatrix(); - float scaleFactor = 0.75f; - GlStateManager.scale(scaleFactor, scaleFactor, 0); - String hint = "(" + EnumChatFormatting.GOLD + EnumChatFormatting.ITALIC + "Note: " + EnumChatFormatting.RESET + "Destroyed Crypts can only be detected up to ~50 blocks away from the player)"; - this.drawCenteredString(this.fontRendererObj, hint, (int) ((this.width / 2) * (1 / scaleFactor)), (int) ((this.height / 2 + 95) * (1 / scaleFactor)), 0x00cccccc); - GlStateManager.popMatrix(); - - super.drawScreen(mouseX, mouseY, partialTicks); - - if (checkBoxShowOverlay.enabled) { - MooConfig.dungOverlayPositionX = sliderX.getValueInt(); - MooConfig.dungOverlayPositionY = sliderY.getValueInt(); - MooConfig.dungOverlayGuiScale = sliderGuiScale.getValueInt(); - } - } - - @Override - protected void keyTyped(char typedChar, int keyCode) throws IOException { - if (keyCode == Keyboard.KEY_ESCAPE) { - resetDungeonOverlayPosition(); - } - super.keyTyped(typedChar, keyCode); - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException { - if (button == buttonCancel) { - resetDungeonOverlayPosition(); - closeGui(); - } else if (button == buttonSave) { - if (wasDungOverlayEnabled != checkBoxShowOverlay.isChecked() || previousPositionX != sliderX.getValueInt() || previousPositionY != sliderY.getValueInt() || previousGuiScale != sliderGuiScale.getValueInt()) { - main.getConfig().syncFromFields(); - if (wasDungOverlayEnabled != checkBoxShowOverlay.isChecked()) { - main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "The dungeon performance overlay is now " + (checkBoxShowOverlay.isChecked() ? EnumChatFormatting.DARK_GREEN + "enabled" : EnumChatFormatting.DARK_RED + "disabled")); - } else { - main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Saved new size and position of the dungeon performance overlay!"); - } - } - closeGui(); - } else if (button == checkBoxShowOverlay) { - sliderX.enabled = checkBoxShowOverlay.isChecked(); - sliderY.enabled = checkBoxShowOverlay.isChecked(); - sliderGuiScale.enabled = checkBoxShowOverlay.isChecked(); - MooConfig.dungOverlayEnabled = checkBoxShowOverlay.isChecked(); - } - } - - private void resetDungeonOverlayPosition() { - MooConfig.dungOverlayEnabled = wasDungOverlayEnabled; - MooConfig.dungOverlayPositionX = previousPositionX; - MooConfig.dungOverlayPositionY = previousPositionY; - MooConfig.dungOverlayGuiScale = previousGuiScale; - } - - private void closeGui() { - this.mc.displayGuiScreen(null); - if (this.mc.currentScreen == null) { - this.mc.setIngameFocus(); - } - } -} diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java index 0a498dd..1d8b4ee 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -2,78 +2,122 @@ package de.cowtipper.cowlection.config; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.command.TabCompletableCommand; +import de.cowtipper.cowlection.config.gui.MooConfigGui; +import de.cowtipper.cowlection.config.gui.MooConfigPreview; +import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.util.MooChatComponent; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.command.ICommand; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagString; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; import net.minecraft.util.Util; import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; -import net.minecraftforge.fml.client.FMLConfigGuiFactory; import net.minecraftforge.fml.client.event.ConfigChangedEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.input.Keyboard; import java.io.File; import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; /** - * Mod configuration via ingame gui + * Mod configuration * <p> * Based on <a href="https://github.com/TheGreyGhost/MinecraftByExample/blob/1-8-9final/src/main/java/minecraftbyexample/mbe70_configuration/MBEConfiguration.java">TheGreyGhost's MinecraftByExample</a> * * @see ForgeModContainer - * @see FMLConfigGuiFactory */ public class MooConfig { - static final String CATEGORY_LOGS_SEARCH = "logssearch"; - // main config + // Category: General + private static String configGuiExplanations; + public static String[] tabCompletableNamesCommands; + private static final String CATEGORY_LOGS_SEARCH = "logssearch"; + public static String[] logsDirs; + private static String defaultStartDate; + // Category: Notifications public static boolean doUpdateCheck; public static boolean showBestFriendNotifications; public static boolean showFriendNotifications; public static boolean showGuildNotifications; public static boolean doBestFriendsOnlineCheck; - public static boolean showAdvancedTooltips; - public static String[] tabCompletableNamesCommands; + // Category: SkyBlock + public static int tooltipToggleKeyBinding; + private static String tooltipAuctionHousePriceEach; + private static String tooltipItemAge; + public static boolean tooltipItemAgeShortened; + private static String tooltipItemTimestamp; private static String numeralSystem; - // SkyBlock dungeon - public static int[] dungClassRange; - public static boolean dungFilterPartiesWithDupes; - public static String dungPartyFinderArmorLookup; - public static String dungItemQualityPos; + // Category: SkyBlock Dungeons + public static int dungItemToolTipToggleKeyBinding; + private static String dungItemQualityPos; public static boolean dungOverlayEnabled; - public static int dungOverlayGuiScale; public static int dungOverlayPositionX; public static int dungOverlayPositionY; - // logs search config - public static String[] logsDirs; - private static String defaultStartDate; - // other stuff - public static String moo; + public static int dungOverlayGuiScale; + public static boolean dungOverlayTextShadow; + public static int dungClassMin; + public static boolean dungFilterPartiesWithArcherDupes; + public static boolean dungFilterPartiesWithBerserkDupes; + public static boolean dungFilterPartiesWithHealerDupes; + public static boolean dungFilterPartiesWithMageDupes; + public static boolean dungFilterPartiesWithTankDupes; + private static String dungPartyFinderArmorLookup; + private static Configuration cfg = null; + private static final List<MooConfigCategory> configCategories = new ArrayList<>(); private final Cowlection main; - private List<String> propOrderGeneral; - private List<String> propOrderLogsSearch; + private Property propTabCompletableNamesCommands; + private List<Property> logSearchProperties; public MooConfig(Cowlection main, Configuration configuration) { this.main = main; cfg = configuration; + + if (cfg.getLoadedConfigVersion() == null || !cfg.getLoadedConfigVersion().equals(cfg.getDefinedConfigVersion())) { + updateConfig(cfg.getLoadedConfigVersion()); + } + initConfig(); } - static Configuration getConfig() { - return cfg; + private void updateConfig(String oldVersion) { + if (oldVersion == null) { + // config of Cowlection v1.8.9-0.10.2 and older + + // leave log search settings as is + + if (cfg.hasCategory(Configuration.CATEGORY_CLIENT)) { + // copy old 'moo' value to new, separate config + if (cfg.hasKey(Configuration.CATEGORY_CLIENT, "moo")) { + String oldMoo = cfg.getString("moo", Configuration.CATEGORY_CLIENT, "00000000-0000-0000-0000-000000000000", "Temporary config entry, should be deleted automatically.", Utils.VALID_UUID_PATTERN); + if (StringUtils.isNotEmpty(oldMoo) && Utils.isValidUuid(oldMoo)) { + // save into new cfg: + main.getMoo().setMooIfValid(oldMoo, false); + } + } + + // delete client category (no longer used) + ConfigCategory oldClientCategory = cfg.getCategory(Configuration.CATEGORY_CLIENT); + cfg.removeCategory(oldClientCategory); + } + cfg.save(); + } } private void initConfig() { @@ -85,37 +129,28 @@ public class MooConfig { * Load the configuration values from the configuration file */ private void syncFromFile() { - syncConfig(true, true); + syncConfig(true, true, true); } /** * Save the GUI-altered values to disk */ - private void syncFromGui() { - syncConfig(false, true); + public void syncFromGui() { + syncConfig(false, true, true); } /** - * Save the Configuration variables (fields) to disk + * Save the GUI-altered values to the properties; don't save to disk - only memory */ - public void syncFromFields() { - syncConfig(false, false); + public void syncFromGuiWithoutSaving() { + syncConfig(false, true, false); } - public static LocalDate calculateStartDate() { - try { - // date format: yyyy-mm-dd - return LocalDate.parse(defaultStartDate); - } catch (DateTimeParseException e) { - // fallthrough - } - try { - int months = Integer.parseInt(defaultStartDate); - return LocalDate.now().minus(months, ChronoUnit.MONTHS); - } catch (NumberFormatException e) { - // default: 1 month - return LocalDate.now().minus(1, ChronoUnit.MONTHS); - } + /** + * Save the Configuration variables (fields) to disk + */ + public void syncFromFields() { + syncConfig(false, false, true); } /** @@ -126,127 +161,321 @@ public class MooConfig { * * @param loadConfigFromFile if true, load the config field from the configuration file on disk * @param readFieldsFromConfig if true, reload the member variables from the config field + * @param saveToFile if true, save changes to config file */ - private void syncConfig(boolean loadConfigFromFile, boolean readFieldsFromConfig) { + @SuppressWarnings("DuplicatedCode") + private void syncConfig(boolean loadConfigFromFile, boolean readFieldsFromConfig, boolean saveToFile) { if (loadConfigFromFile) { cfg.load(); } - // config section: main configuration - propOrderGeneral = new ArrayList<>(); - - Property propDoUpdateCheck = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "doUpdateCheck", true, "Check for mod updates?"), true); - Property propShowBestFriendNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "showBestFriendNotifications", true, "Set to true to receive best friends' login/logout messages, set to false hide them."), true); - Property propShowFriendNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "showFriendNotifications", true, "Set to true to receive friends' login/logout messages, set to false hide them."), true); - Property propShowGuildNotifications = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "showGuildNotifications", true, "Set to true to receive guild members' login/logout messages, set to false hide them."), true); - Property propDoBestFriendsOnlineCheck = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "doBestFriendsOnlineCheck", true, "Set to true to check best friends' online status when joining a server, set to false to disable."), true); - Property propShowAdvancedTooltips = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "showAdvancedTooltips", true, "Set to true to show advanced tooltips, set to false show default tooltips."), true); - Property propNumeralSystem = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "numeralSystem", "Arabic numerals: 1, 4, 10", "Use Roman or Arabic numeral system?", new String[]{"Arabic numerals: 1, 4, 10", "Roman numerals: I, IV, X"}), true); - Property propTabCompletableNamesCommands = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "tabCompletableNamesCommands", new String[]{"party", "p", "invite", "visit", "ah", "ignore", "msg", "tell", "w", "boop", "profile", "friend", "friends", "f"}, "List of commands with a Tab-completable username argument."), true) - .setValidationPattern(Pattern.compile("^[A-Za-z]+$")); - Property propMoo = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "moo", "", "The answer to life the universe and everything. Don't edit this entry manually!", Utils.VALID_UUID_PATTERN), false); - - // SkyBlock dungeon - Property propDungClassRange = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungClassRange", new int[]{-1, -1}, "Accepted level range for the dungeon party finder. Set to -1 to disable"), true) - .setMinValue(-1).setIsListLengthFixed(true); - Property propDungFilterPartiesWithDupes = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungFilterPartiesWithDupes", false, "Mark parties with duplicated classes?"), true); - Property propDungPartyFinderArmorLookup = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungPartyFinderArmorLookup", "as a tooltip", "Show armor of player joining via party finder as a tooltip or in chat?", new String[]{"as a tooltip", "in chat", "disabled"}), true); - Property propDungItemQualityPos = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungItemQualityPos", "top", "Position of item quality in tooltip", new String[]{"top", "bottom"}), true); - Property propDungOverlayEnabled = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungOverlayEnabled", true, "Enable Dungeon performance overlay?"), false); - Property propDungOverlayPositionX = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungGuiPositionX", 5, "Dungeon performance overlay position: x value", -1, 10000), false); - Property propDungOverlayPositionY = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungGuiPositionY", 5, "Dungeon performance overlay position: y value", -1, 5000), false); - Property propDungOverlayGuiScale = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, - "dungOverlayGuiScale", 100, "Dungeon performance overlay GUI scale", 50, 200), false); - cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral); - - // config section: log files search - propOrderLogsSearch = new ArrayList<>(); - - Property propLogsDirs = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, + // reset previous entries + configCategories.clear(); + + // Category: General + MooConfigCategory configCat = new MooConfigCategory("General", "general"); + configCategories.add(configCat); + + // Sub-Category: Cowlection config gui + MooConfigCategory.SubCategory subCat = configCat.addSubCategory("Cowlection config gui"); + subCat.addExplanations("Display of the explanations for each sub-section:", + " ⣠" + EnumChatFormatting.YELLOW + "as tooltip â " + EnumChatFormatting.DARK_GRAY + "âŹ" + EnumChatFormatting.RESET + " = tooltip when hovering over sub-category heading (with darkened background)", + " ⣠" + EnumChatFormatting.YELLOW + "as tooltip âĄ" + EnumChatFormatting.WHITE + "âŹ" + EnumChatFormatting.RESET + " = tooltip when hovering over sub-category heading (no extra background)", + " ⣠" + EnumChatFormatting.YELLOW + "as text" + EnumChatFormatting.RESET + " = below each sub-category heading", + " ⣠" + EnumChatFormatting.YELLOW + "hidden" + EnumChatFormatting.RESET + " = "); + Property propConfigGuiExplanations = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "configGuiExplanations", "tooltip â §0âŹ", "Display config settings explanations", + new String[]{"as tooltip â §0âŹ", "as tooltip âĄÂ§fâŹ", "as text", "hidden"})); + + // Sub-Category: API settings + subCat = configCat.addSubCategory("API settings"); + subCat.addExplanations("Some features use the official Hypixel API and therefore require your API key.", + "Use " + EnumChatFormatting.YELLOW + "/moo apikey " + EnumChatFormatting.RESET + "to see how to request a new API key from Hypixel", + "The API key is stored " + EnumChatFormatting.ITALIC + "locally " + EnumChatFormatting.ITALIC + "on your computer."); + subCat.addConfigEntry(main.getMoo().getPropIsMooValid()); + + // Sub-Category: Tab-completable names in commands + subCat = configCat.addSubCategory("Tab-completable usernames"); + subCat.addExplanations("For certain commands you can use " + EnumChatFormatting.YELLOW + "TAB " + EnumChatFormatting.RESET + "to autocomplete player names", + EnumChatFormatting.UNDERLINE + "Uses player names from:", + " ⣠Guild and Party chat", + " ⣠Party and game (duels) invites", + " ⣠SkyBlock Dungeon party finder: when a player joins the group", + " ⣠Online best friends (if the best friend online checker is enabled)"); + + propTabCompletableNamesCommands = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tabCompletableNamesCommands", new String[]{"party", "p", "invite", "visit", "ah", "ignore", "msg", "tell", "w", "boop", "profile", "friend", "friends"}, "List of commands with a Tab-completable username argument.") + .setValidationPattern(Pattern.compile("^[A-Za-z]+$"))); + + // Sub-Category: Other settings + subCat = configCat.addSubCategory("Other settings"); + subCat.addExplanations("Other settings that are located in other GUIs"); + + Property propLogsDirs = subCat.addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, "logsDirs", resolveDefaultLogsDirs(), - "Directories with Minecraft log files"), true, CATEGORY_LOGS_SEARCH); - Property propDefaultStartDate = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, - "defaultStartDate", "3", "Default start date (a number means X months ago, alternatively a fixed date Ă la yyyy-mm-dd can be used)"), true) + "Directories with Minecraft log files")); + Property propDefaultStartDate = subCat.addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, + "defaultStartDate", "3", "Default start date (a number means X months ago, alternatively a fixed date Ă la yyyy-mm-dd can be used)")) .setValidationPattern(Pattern.compile("^[1-9][0-9]{0,2}|(2[0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))$")); + logSearchProperties = new ArrayList<>(); + logSearchProperties.add(propLogsDirs); + logSearchProperties.add(propDefaultStartDate); + + // Category: Notifications + configCat = new MooConfigCategory("Notifications", "notifications"); + configCategories.add(configCat); + + // Sub-Category: Mod update checker + subCat = configCat.addSubCategory("Mod update checker"); + Property propDoUpdateCheck = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "doUpdateCheck", true, "Check for mod updates?")); + + // Sub-Category: Login & Logout + subCat = configCat.addSubCategory("Login & Logout"); + subCat.addExplanations("Hides selected login/logout notifications ", + "while still showing notifications of best friends (if enabled).", + "Add someone to the best friends list with " + EnumChatFormatting.YELLOW + "/moo add <player>" + EnumChatFormatting.RESET); + + Property propShowBestFriendNotifications = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "showBestFriendNotifications", true, "Set to true to receive best friends' login/logout messages, set to false hide them."), + new MooConfigPreview(new ChatComponentText("§a§lBest friend §a> §6Cow §r§ejoined."))); + Property propShowFriendNotifications = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "showFriendNotifications", true, "Set to true to receive friends' login/logout messages, set to false hide them."), + new MooConfigPreview(new ChatComponentText("§aFriend > §r§aBob §ejoined."))); + Property propShowGuildNotifications = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "showGuildNotifications", true, "Set to true to receive guild members' login/logout messages, set to false hide them."), + new MooConfigPreview(new ChatComponentText("§2Guild > §r§7Herobrian §eleft."))); + + + // Sub-Category: Best friends online status + subCat = configCat.addSubCategory("Best friend online checker"); + subCat.addExplanations("Check which best friends are online when you join the server.", + "About once a day, a check for new name changes is also performed automatically."); + + IChatComponent spacer = new MooChatComponent(", ").green(); + Property propDoBestFriendsOnlineCheck = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "doBestFriendsOnlineCheck", true, "Set to true to check best friends' online status when joining a server, set to false to disable."), + new MooConfigPreview(new MooChatComponent("§a⏤ Online best friends (§24§a/§216§a): ") + .appendSibling(MooConfigPreview.createDemoOnline("Alice", "Housing", "1 hour 13 minutes 37 seconds")).appendSibling(spacer) + .appendSibling(MooConfigPreview.createDemoOnline("Bob", "Build Battle", "2 hours 13 minutes 37 seconds")).appendSibling(spacer) + .appendSibling(MooConfigPreview.createDemoOnline("Cow", "SkyBlock", "13 minutes 37 seconds")).appendSibling(spacer) + .appendSibling(MooConfigPreview.createDemoOnline("Herobrian", "Murder Mystery", "13 hours 33 minutes 37 seconds")))); + + + // Category: SkyBlock + configCat = new MooConfigCategory("SkyBlock", "skyblock"); + configCategories.add(configCat); + + // Sub-Category: Tooltip enhancements + subCat = configCat.addSubCategory("Tooltip enhancements"); + + Property propTooltipToggleKeyBinding = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tooltipToggleKeyBinding", Keyboard.KEY_LSHIFT, "Key to toggle tooltip")); + + Property propTooltipAuctionHousePriceEach = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tooltipAuctionHousePriceEach", "always", "Add price per item if multiple items are bought or sold", new String[]{"always", "key press", "never"})); + + Map<String, NBTBase> demoItemExtraAttributes = new HashMap<>(); + demoItemExtraAttributes.put("new_years_cake", new NBTTagInt(1)); + demoItemExtraAttributes.put("originTag", new NBTTagString("REWARD_NEW_YEARS_CAKE_NPC")); + demoItemExtraAttributes.put("id", new NBTTagString("NEW_YEAR_CAKE")); + demoItemExtraAttributes.put("uuid", new NBTTagString("64b3a60b-74f2-4ebd-818d-d019c5b7f3e0")); + demoItemExtraAttributes.put("timestamp", new NBTTagString("6/16/19 5:05 PM")); + MooConfigPreview nonStackableItemPreview = new MooConfigPreview(MooConfigPreview.createDemoItem("cake", "§dNew Year Cake", new String[]{"§7Given to every player as a", "§7celebration for the 1st SkyBlock", "§7year!", "", "§d§lSPECIAL"}, demoItemExtraAttributes)); + + Property propTooltipItemAge = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tooltipItemAge", "always", "Show item age", new String[]{"always", "key press", "never"}), + nonStackableItemPreview); + + Property propTooltipItemAgeShortened = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tooltipItemAgeShortened", true, "Shorten item age?")); + + Property propTooltipItemTimestamp = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "tooltipItemTimestamp", "key press", "Show item creation date", new String[]{"always", "key press", "never"})); + + Property propNumeralSystem = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "numeralSystem", "Arabic: 1, 4, 10", "Use Roman or Arabic numeral system?", new String[]{"Arabic: 1, 4, 10", "Roman: I, IV, X"})); + + + // Category: SkyBlock Dungeons + configCat = new MooConfigCategory("SkyBlock Dungeons", "skyblockdungeons"); + configCat.setMenuDisplayName("SB Dungeons"); + configCategories.add(configCat); + + // Sub-Category: Tooltip enhancements + subCat = configCat.addSubCategory("Dungeon item tooltip enhancements"); + subCat.addExplanations("Hold left " + EnumChatFormatting.YELLOW + "SHIFT " + EnumChatFormatting.RESET + "while hovering over a dungeon item.", + "Shows " + EnumChatFormatting.YELLOW + "item quality " + EnumChatFormatting.RESET + "and " + EnumChatFormatting.YELLOW + "dungeon floor" + EnumChatFormatting.RESET + ", also remove stats from reforges and essences (âŞ)", + "which normally makes the comparison of dungeon items difficult.", + "Instead, the tooltip shows...", + " ⣠base/default stats " + EnumChatFormatting.GRAY + "(outside dungeons; 1st value - usually red or green)", + " ⣠stats inside dungeons " + EnumChatFormatting.GRAY + "(including dungeon level stat boost, but without essences [âââŞ])", + " ⣠stats inside dungeons with 5x essence upgrades " + EnumChatFormatting.GRAY + "(â
ââŞ)"); + + Property propDungItemToolTipToggleKeyBinding = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungItemToolTipToggleKeyBinding", Keyboard.KEY_LSHIFT, "Key to toggle dungeon item tooltip")); + + Property propDungItemQualityPos = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungItemQualityPos", "top", "Position of item quality in tooltip", new String[]{"top", "bottom"}), + new MooConfigPreview( + MooConfigPreview.createDungeonItem("light", "7/17/20 7:22 PM", "§7Gear Score: §d336 §8(526)", "§7Crit Chance: §c+5% §9(Light +2%)", "§7Crit Damage: §c+30% §9(Light +4%) §8(+48.9%)", "§7Bonus Attack Speed: §c+4% §9(Light +4%)", "", "§7Health: §a+126 HP §9(Light +15 HP) §8(+205.38 HP)", "§7Defense: §a+76 §9(Light +4) §8(+123.88)", "§7Speed: §a+4 §9(Light +4) §8(+6.52)", "", "§9Growth V, §9Protection V", "§9Thorns III", "", "§7Increase the damage you deal", "§7with arrows by §c5%§7.", "", "§6Full Set Bonus: Skeleton Soldier", "§7Increase the damage you deal", "§7with arrows by an extra §c25%§7.", "", "§aPerfect 52500 / 52500", "§5§lEPIC DUNGEON LEGGINGS"), + MooConfigPreview.createDungeonItem("clean", "7/11/20 12:27 PM", "§7Gear Score: §d359 §8(561)", "§7Crit Chance: §c+11% §9(Clean +8%)", "§7Crit Damage: §c+26% §8(+42.38%)", "", "§7Health: §a+126 HP §9(Clean +15 HP) §8(+205.38 HP)", "§7Defense: §a+87 §9(Clean +15) §8(+141.81)", "", "§9Growth V, §9Protection V", "§9Thorns III", "", "§7Increase the damage you deal", "§7with arrows by §c5%§7.", "", "§6Full Set Bonus: Skeleton Soldier", "§7Increase the damage you deal", "§7with arrows by an extra §c25%§7.", "", "§aPerfect 52500 / 52500", "§5§lEPIC DUNGEON LEGGINGS"))); + + // Sub-Category: Performance Overlay + subCat = configCat.addSubCategory("Performance Overlay"); + subCat.addExplanations(EnumChatFormatting.UNDERLINE + "Keeps track of:", + " ⣠skill score " + EnumChatFormatting.GRAY + "(reduced by deaths and failed puzzles)", + " ⣠speed score " + EnumChatFormatting.GRAY + "(-2.2 points when over 20 minutes)", + " ⣠bonus score " + EnumChatFormatting.GRAY + "(+1 [max 5] for each destroyed crypt; can only be detected up to ~50 blocks away from the player)", + "Does " + EnumChatFormatting.ITALIC + "not" + EnumChatFormatting.RESET + " track explorer score " + EnumChatFormatting.GRAY + "(explored rooms, secrets, ...)"); + + Property propDungOverlayEnabled = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungOverlayEnabled", true, "Enable Dungeon performance overlay?")); + + Property propDungOverlayPositionX = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungOverlayPositionX", 1, "Dungeon performance overlay position: x value", 0, 1000), + null, "â°", // per mille + (slider) -> { + MooConfig.dungOverlayPositionX = slider.getValueInt(); + MooConfigGui.showDungeonPerformanceOverlayUntil = System.currentTimeMillis() + 500; + }); + + Property propDungOverlayPositionY = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungOverlayPositionY", 1, "Dungeon performance overlay position: y value", 0, 1000), + null, "â°", // per mille + (slider) -> { + MooConfig.dungOverlayPositionY = slider.getValueInt(); + MooConfigGui.showDungeonPerformanceOverlayUntil = System.currentTimeMillis() + 500; + }); + + Property propDungOverlayGuiScale = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungOverlayGuiScale", 100, "Dungeon performance overlay GUI scale", 50, 200), + null, "%", + (slider) -> { + MooConfig.dungOverlayGuiScale = slider.getValueInt(); + MooConfigGui.showDungeonPerformanceOverlayUntil = System.currentTimeMillis() + 500; + }); + + Property propDungOverlayTextShadow = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungOverlayTextShadow", true, "Dungeon performance overlay GUI scale")); + + // Sub-Category: Party Finder + subCat = configCat.addSubCategory("Dungeon Party Finder"); + subCat.addExplanations("Adds various indicators to the dungeon party finder", + "to make it easier to find the perfect party:", + "", + "Marks parties...", + " ⣠you cannot join: " + EnumChatFormatting.RED + "âŹ" + EnumChatFormatting.RESET + " (red carpet)", + " ⣠with someone below a certain class level: " + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "áŻ" + EnumChatFormatting.RESET, + " ⣠with duplicated roles you specify below: " + EnumChatFormatting.GOLD + "²âş", + " ⣠that match your criteria: " + EnumChatFormatting.GREEN + "âŹ" + EnumChatFormatting.RESET + " (green carpet)"); + + Property propDungClassMin = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungClassMin", 0, "Marks parties with members with lower class level than this value") + .setMinValue(0).setMaxValue(50), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "áŻ").gray())); + + Property propDungFilterPartiesWithArcherDupes = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungFilterPartiesWithArcherDupes", true, "Mark parties with duplicated Archer class?"), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.GOLD + "²âşA").gray())); + + Property propDungFilterPartiesWithBerserkDupes = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungFilterPartiesWithBerserkDupes", false, "Mark parties with duplicated Berserk class?"), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.GOLD + "²âşB").gray())); + + Property propDungFilterPartiesWithHealerDupes = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungFilterPartiesWithHealerDupes", false, "Mark parties with duplicated Healer class?"), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.GOLD + "²âşH").gray())); + + Property propDungFilterPartiesWithMageDupes = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungFilterPartiesWithMageDupes", false, "Mark parties with duplicated Mage class?"), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.GOLD + "²âşM").gray())); + + Property propDungFilterPartiesWithTankDupes = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungFilterPartiesWithTankDupes", false, "Mark parties with duplicated Tank class?"), + new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.GOLD + "²âşT").gray())); + + Property propDungPartyFinderArmorLookup = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungPartyFinderArmorLookup", "as a tooltip", "Show armor of player joining via party finder as a tooltip or in chat?", new String[]{"as a tooltip", "in chat", "disabled"})); - cfg.setCategoryPropertyOrder(CATEGORY_LOGS_SEARCH, propOrderLogsSearch); - - // 'manual' replacement for propTabCompletableNamesCommands.hasChanged() boolean modifiedTabCompletableCommandsList = false; String[] tabCompletableCommandsPreChange = tabCompletableNamesCommands != null ? tabCompletableNamesCommands.clone() : null; if (readFieldsFromConfig) { - // main config + // Category: General + configGuiExplanations = propConfigGuiExplanations.getString(); + tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList(); + logsDirs = propLogsDirs.getStringList(); + defaultStartDate = propDefaultStartDate.getString().trim(); + // Category: Notifications doUpdateCheck = propDoUpdateCheck.getBoolean(); showBestFriendNotifications = propShowBestFriendNotifications.getBoolean(); showFriendNotifications = propShowFriendNotifications.getBoolean(); showGuildNotifications = propShowGuildNotifications.getBoolean(); doBestFriendsOnlineCheck = propDoBestFriendsOnlineCheck.getBoolean(); - showAdvancedTooltips = propShowAdvancedTooltips.getBoolean(); + // Category: SkyBlock + tooltipToggleKeyBinding = propTooltipToggleKeyBinding.getInt(); + tooltipAuctionHousePriceEach = propTooltipAuctionHousePriceEach.getString(); + tooltipItemAge = propTooltipItemAge.getString(); + tooltipItemAgeShortened = propTooltipItemAgeShortened.getBoolean(); + tooltipItemTimestamp = propTooltipItemTimestamp.getString(); numeralSystem = propNumeralSystem.getString(); - tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList(); - moo = propMoo.getString(); - - // SkyBlock dungeon - dungClassRange = propDungClassRange.getIntList(); - dungFilterPartiesWithDupes = propDungFilterPartiesWithDupes.getBoolean(); - dungPartyFinderArmorLookup = propDungPartyFinderArmorLookup.getString(); + // Category: SkyBlock Dungeons + dungItemToolTipToggleKeyBinding = propDungItemToolTipToggleKeyBinding.getInt(); dungItemQualityPos = propDungItemQualityPos.getString(); dungOverlayEnabled = propDungOverlayEnabled.getBoolean(); dungOverlayPositionX = propDungOverlayPositionX.getInt(); dungOverlayPositionY = propDungOverlayPositionY.getInt(); dungOverlayGuiScale = propDungOverlayGuiScale.getInt(); + dungOverlayTextShadow = propDungOverlayTextShadow.getBoolean(); + dungClassMin = propDungClassMin.getInt(); + dungFilterPartiesWithArcherDupes = propDungFilterPartiesWithArcherDupes.getBoolean(); + dungFilterPartiesWithBerserkDupes = propDungFilterPartiesWithBerserkDupes.getBoolean(); + dungFilterPartiesWithHealerDupes = propDungFilterPartiesWithHealerDupes.getBoolean(); + dungFilterPartiesWithMageDupes = propDungFilterPartiesWithMageDupes.getBoolean(); + dungFilterPartiesWithTankDupes = propDungFilterPartiesWithTankDupes.getBoolean(); + dungPartyFinderArmorLookup = propDungPartyFinderArmorLookup.getString(); - // logs search config - logsDirs = propLogsDirs.getStringList(); - defaultStartDate = propDefaultStartDate.getString().trim(); if (!Arrays.equals(tabCompletableCommandsPreChange, tabCompletableNamesCommands)) { modifiedTabCompletableCommandsList = true; } } - // main config + // Category: General + propConfigGuiExplanations.set(configGuiExplanations); + propTabCompletableNamesCommands.set(tabCompletableNamesCommands); + propLogsDirs.set(logsDirs); + propDefaultStartDate.set(defaultStartDate); + // Category: Notifications propDoUpdateCheck.set(doUpdateCheck); propShowBestFriendNotifications.set(showBestFriendNotifications); propShowFriendNotifications.set(showFriendNotifications); propShowGuildNotifications.set(showGuildNotifications); propDoBestFriendsOnlineCheck.set(doBestFriendsOnlineCheck); - propShowAdvancedTooltips.set(showAdvancedTooltips); + // Category: SkyBlock + propTooltipToggleKeyBinding.set(tooltipToggleKeyBinding); + propTooltipAuctionHousePriceEach.set(tooltipAuctionHousePriceEach); + propTooltipItemAge.set(tooltipItemAge); + propTooltipItemAgeShortened.set(tooltipItemAgeShortened); + propTooltipItemTimestamp.set(tooltipItemTimestamp); propNumeralSystem.set(numeralSystem); - propTabCompletableNamesCommands.set(tabCompletableNamesCommands); - propMoo.set(moo); - - // SkyBlock dungeon - propDungClassRange.set(dungClassRange); - propDungFilterPartiesWithDupes.set(dungFilterPartiesWithDupes); - propDungPartyFinderArmorLookup.set(dungPartyFinderArmorLookup); + // Category: SkyBlock Dungeons + propDungItemToolTipToggleKeyBinding.set(dungItemToolTipToggleKeyBinding); propDungItemQualityPos.set(dungItemQualityPos); propDungOverlayEnabled.set(dungOverlayEnabled); propDungOverlayPositionX.set(dungOverlayPositionX); propDungOverlayPositionY.set(dungOverlayPositionY); propDungOverlayGuiScale.set(dungOverlayGuiScale); + propDungOverlayTextShadow.set(dungOverlayTextShadow); + propDungClassMin.set(dungClassMin); + propDungFilterPartiesWithArcherDupes.set(dungFilterPartiesWithArcherDupes); + propDungFilterPartiesWithBerserkDupes.set(dungFilterPartiesWithBerserkDupes); + propDungFilterPartiesWithHealerDupes.set(dungFilterPartiesWithHealerDupes); + propDungFilterPartiesWithMageDupes.set(dungFilterPartiesWithMageDupes); + propDungFilterPartiesWithTankDupes.set(dungFilterPartiesWithTankDupes); + propDungPartyFinderArmorLookup.set(dungPartyFinderArmorLookup); - // logs search config - propLogsDirs.set(logsDirs); - propDefaultStartDate.set(defaultStartDate); - - if (cfg.hasChanged()) { + if (saveToFile && cfg.hasChanged()) { boolean isPlayerIngame = Minecraft.getMinecraft().thePlayer != null; if (modifiedTabCompletableCommandsList) { if (isPlayerIngame) { @@ -269,31 +498,9 @@ public class MooConfig { propTabCompletableNamesCommands.set(tabCompletableNamesCommands); } } - if (isPlayerIngame && dungClassRange[0] > -1 && dungClassRange[1] > -1 && dungClassRange[0] > dungClassRange[1]) { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Dungeon class range minimum value cannot be higher than the maximum value."); - } - cfg.save(); - } - } - private Property addConfigEntry(Property property, boolean showInGui, String category) { - if (showInGui) { - property.setLanguageKey(Cowlection.MODID + ".config." + property.getName()); - } else { - property.setShowInGui(false); - } - - if (CATEGORY_LOGS_SEARCH.equals(category)) { - propOrderLogsSearch.add(property.getName()); - } else { - // == Configuration.CATEGORY_CLIENT: - propOrderGeneral.add(property.getName()); + cfg.save(); } - return property; - } - - private Property addConfigEntry(Property property, boolean showInGui) { - return addConfigEntry(property, showInGui, Configuration.CATEGORY_CLIENT); } /** @@ -329,6 +536,13 @@ public class MooConfig { return logsDirs.toArray(new String[]{}); } + // Category: General + public static Setting getConfigGuiExplanationsDisplay() { + return Setting.get(configGuiExplanations); + } + + // Category: Notifications + /** * Should login/logout notifications be modified and thus monitored? * @@ -338,18 +552,88 @@ public class MooConfig { return showBestFriendNotifications || !showFriendNotifications || !showGuildNotifications; } + // Category: SkyBlock + public static Setting getTooltipAuctionHousePriceEachDisplay() { + return Setting.get(tooltipAuctionHousePriceEach); + } + + public static Setting getTooltipItemAgeDisplay() { + return Setting.get(tooltipItemAge); + } + + public static Setting getTooltipItemTimestampDisplay() { + return Setting.get(tooltipItemTimestamp); + } + public static boolean useRomanNumerals() { return numeralSystem.startsWith("Roman"); } + public static boolean isTooltipToggleKeyBindingPressed() { + return tooltipToggleKeyBinding > 0 && Keyboard.isKeyDown(MooConfig.tooltipToggleKeyBinding); + } + + public static boolean isDungeonItemTooltipToggleKeyBindingPressed() { + return dungItemToolTipToggleKeyBinding > 0 && Keyboard.isKeyDown(MooConfig.dungItemToolTipToggleKeyBinding); + } + + // Category: SkyBlock Dungeons public static boolean isDungItemQualityAtTop() { return dungItemQualityPos.equals("top"); } - public static boolean showArmorLookupInChat() { - return "in chat".equals(dungPartyFinderArmorLookup); + public static Setting getDungPartyFinderArmorLookupDisplay() { + return Setting.get(dungPartyFinderArmorLookup); + } + + public static boolean filterDungPartiesWithDupes(DataHelper.DungeonClass dungeonClass) { + switch (dungeonClass) { + case ARCHER: + return dungFilterPartiesWithArcherDupes; + case BERSERK: + return dungFilterPartiesWithBerserkDupes; + case HEALER: + return dungFilterPartiesWithHealerDupes; + case MAGE: + return dungFilterPartiesWithMageDupes; + case TANK: + return dungFilterPartiesWithTankDupes; + default: + return false; + } } + // MC Log Search: + public static LocalDate calculateStartDate() { + try { + // date format: yyyy-mm-dd + return LocalDate.parse(defaultStartDate); + } catch (DateTimeParseException e) { + // fallthrough + } + try { + int months = Integer.parseInt(defaultStartDate); + return LocalDate.now().minus(months, ChronoUnit.MONTHS); + } catch (NumberFormatException e) { + // default: 1 month + return LocalDate.now().minus(1, ChronoUnit.MONTHS); + } + } + + // other stuff + public static List<MooConfigCategory> getConfigCategories() { + return configCategories; + } + + public Property getTabCompletableNamesCommandsProperty() { + return propTabCompletableNamesCommands; + } + + public List<Property> getLogSearchProperties() { + return logSearchProperties; + } + + public class ConfigEventHandler { @SubscribeEvent(priority = EventPriority.NORMAL) public void onEvent(ConfigChangedEvent.OnConfigChangedEvent e) { @@ -358,4 +642,32 @@ public class MooConfig { } } } + + public enum Setting { + UNKNOWN, DISABLED, // + ALWAYS, TOOLTIP, TEXT, // + SPECIAL; + + public static Setting get(String configValue) { + switch (configValue) { + case "always": + return ALWAYS; + case "as a tooltip": + case "as tooltip âĄÂ§fâŹ": + return TOOLTIP; + case "in chat": + case "as text": + return TEXT; + case "hidden": + case "never": + case "disabled": + return DISABLED; + case "key press": + case "as tooltip â §0âŹ": + return SPECIAL; + default: + return UNKNOWN; + } + } + } } diff --git a/src/main/java/de/cowtipper/cowlection/config/MooGuiConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooGuiConfig.java deleted file mode 100644 index 3db7cf3..0000000 --- a/src/main/java/de/cowtipper/cowlection/config/MooGuiConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -package de.cowtipper.cowlection.config; - -import de.cowtipper.cowlection.Cowlection; -import de.cowtipper.cowlection.search.GuiTooltip; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.GuiTextField; -import net.minecraft.client.resources.I18n; -import net.minecraft.util.EnumChatFormatting; -import net.minecraftforge.common.config.ConfigElement; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.fml.client.config.GuiConfig; -import net.minecraftforge.fml.client.config.GuiConfigEntries; -import net.minecraftforge.fml.client.config.IConfigElement; -import org.apache.commons.lang3.reflect.FieldUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class MooGuiConfig extends GuiConfig { - private GuiTooltip defaultStartDateTooltip; - private GuiTextField textFieldDefaultStartDate; - private String defaultStartDateTooltipText; - - public MooGuiConfig(GuiScreen parent) { - super(parent, - getConfigElements(), - Cowlection.MODID, - false, - false, - EnumChatFormatting.BOLD + "Configuration for " + Cowlection.MODNAME); - titleLine2 = EnumChatFormatting.GRAY + GuiConfig.getAbridgedConfigPath(MooConfig.getConfig().toString()); - } - - private static List<IConfigElement> getConfigElements() { - List<IConfigElement> list = new ArrayList<>(new ConfigElement(MooConfig.getConfig().getCategory(Configuration.CATEGORY_CLIENT)).getChildElements()); - list.addAll(new ConfigElement(MooConfig.getConfig().getCategory(MooConfig.CATEGORY_LOGS_SEARCH)).getChildElements()); - return list; - } - - @Override - public void initGui() { - super.initGui(); - // optional: add buttons and initialize fields - for (GuiConfigEntries.IConfigEntry configEntry : entryList.listEntries) { - if ("defaultStartDate".equals(configEntry.getName()) && configEntry instanceof GuiConfigEntries.StringEntry) { - GuiConfigEntries.StringEntry entry = (GuiConfigEntries.StringEntry) configEntry; - defaultStartDateTooltipText = I18n.format(configEntry.getConfigElement().getLanguageKey() + ".tooltip"); - try { - textFieldDefaultStartDate = (GuiTextField) FieldUtils.readField(entry, "textFieldValue", true); - defaultStartDateTooltip = null; - } catch (IllegalAccessException e) { - // wasn't able to access textField, abort drawing tooltip - return; - } - } - } - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - super.drawScreen(mouseX, mouseY, partialTicks); - // optional: create animations, draw additional elements, etc. - - // add tooltip to defaultStartDate textField - if (textFieldDefaultStartDate != null) { - if (defaultStartDateTooltip == null) { - if (textFieldDefaultStartDate.yPosition == 0) { - return; - } - // create GuiTooltip here instead in initGui because y-position of textField is 0 inside initGui - defaultStartDateTooltip = new GuiTooltip(textFieldDefaultStartDate, Arrays.asList(defaultStartDateTooltipText.split("\\\\n"))); - } else if (defaultStartDateTooltip.checkHover(mouseX, mouseY)) { - drawHoveringText(defaultStartDateTooltip.getText(), mouseX, mouseY, fontRendererObj); - } - } - } - - @Override - protected void actionPerformed(GuiButton button) { - super.actionPerformed(button); - // optional: process any additional buttons added in initGui - } -} diff --git a/src/main/java/de/cowtipper/cowlection/config/MooGuiFactory.java b/src/main/java/de/cowtipper/cowlection/config/MooGuiFactory.java deleted file mode 100644 index eedd676..0000000 --- a/src/main/java/de/cowtipper/cowlection/config/MooGuiFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.cowtipper.cowlection.config; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraftforge.fml.client.IModGuiFactory; - -import java.util.Set; - -public class MooGuiFactory implements IModGuiFactory { - @Override - public void initialize(Minecraft minecraftInstance) { - - } - - @Override - public Class<? extends GuiScreen> mainConfigGuiClass() { - return MooGuiConfig.class; - } - - @Override - public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() { - return null; - } - - @Override - public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) { - return null; - } -} diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java new file mode 100644 index 0000000..d362502 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java @@ -0,0 +1,856 @@ +package de.cowtipper.cowlection.config.gui; + +import com.google.common.collect.Lists; +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.config.MooConfigCategory; +import de.cowtipper.cowlection.search.GuiSearch; +import de.cowtipper.cowlection.util.GuiHelper; +import de.cowtipper.cowlection.util.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.MathHelper; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.config.GuiButtonExt; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiSlider; +import net.minecraftforge.fml.client.config.HoverChecker; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.input.Keyboard; + +import java.util.*; + +import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR; +import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR; + +/** + * Based on {@link net.minecraft.client.gui.GuiKeyBindingList} + */ +public class MooConfigCategoryScrolling extends GuiListExtended { + private final Minecraft mc; + private final List<IGuiListEntry> listEntries; + private final Map<Integer, List<String>> explanations; + private final MooConfigGui parent; + private final int marginLeft; + private int maxListLabelWidth = 0; + private KeyBindingConfigEntry currentKeyBindingConfigEntry = null; + /** + * listEntryIndex => (sub-category) preview + */ + private final NavigableMap<Integer, MooConfigPreview> listEntriesPreviews; + + public MooConfigCategoryScrolling(MooConfigGui parent, Minecraft mc, MooConfigCategory currentConfigCategory, int marginLeft) { + super(mc, parent.width - marginLeft, parent.height, 32, parent.height - 5, 20); + this.parent = parent; + this.marginLeft = marginLeft; + setSlotXBoundsFromLeft(marginLeft); + this.mc = mc; + listEntriesPreviews = new TreeMap<>(); + explanations = new HashMap<>(); + + this.listEntries = new ArrayList<>(); + for (MooConfigCategory.SubCategory subCategory : currentConfigCategory.getSubCategories()) { + int subCategoryStartIndex = this.listEntries.size(); + this.listEntries.add(new MooConfigCategoryScrolling.CategoryEntry(subCategory.getDisplayName(), !subCategory.getExplanations().isEmpty())); + + explanations.put(subCategoryStartIndex, subCategory.getExplanations()); + int explanationsLineCount = 0; + if (subCategory.getExplanations().size() > 0 && MooConfig.getConfigGuiExplanationsDisplay() == MooConfig.Setting.TEXT) { + Iterator<String> explanationsIterator = subCategory.getExplanations().iterator(); + explanationsIterator.next(); // skip first entry (= sub category name) + while (explanationsIterator.hasNext()) { + String msgLine = explanationsIterator.next(); + if (explanationsIterator.hasNext()) { + msgLine += "\n" + explanationsIterator.next(); + } + this.listEntries.add(new ExplanationsEntry(msgLine)); + ++explanationsLineCount; + } + } + + // add control buttons to navigate to other guis + if ("Other settings".equals(subCategory.getDisplayName())) { + this.listEntries.add(new GuiSwitchEntry("gotoKeyBindings", "Controls", () -> mc.displayGuiScreen(new GuiControls(MooConfigCategoryScrolling.this.parent, mc.gameSettings)))); + this.listEntries.add(new GuiSwitchEntry("gotoLogSearchConfig", "Log Search", () -> { + mc.displayGuiScreen(new GuiSearch(Cowlection.getInstance().getConfigDirectory(), "")); + })); + continue; // don't add properties to main config gui + } + + // add previews + Map<Integer, MooConfigPreview> previews = subCategory.getPreviews(); + this.listEntriesPreviews.put(subCategoryStartIndex, null); + // previews for specific properties + for (Map.Entry<Integer, MooConfigPreview> previewEntry : previews.entrySet()) { + this.listEntriesPreviews.put(subCategoryStartIndex + explanationsLineCount + previewEntry.getKey(), previewEntry.getValue()); + } + + // add config elements + for (Property configEntry : subCategory.getConfigEntries()) { + int labelWidth = mc.fontRendererObj.getStringWidth(I18n.format(configEntry.getLanguageKey())); + + if (labelWidth > this.maxListLabelWidth) { + this.maxListLabelWidth = labelWidth; + } + + Property.Type type = configEntry.getType(); + if (configEntry.isList() && type == Property.Type.STRING && configEntry.equals(Cowlection.getInstance().getConfig().getTabCompletableNamesCommandsProperty())) { + this.listEntries.add(new GuiSwitchEntry("tabCompletableNamesCommands", "⥠modify", () -> + mc.displayGuiScreen(new GuiConfig(MooConfigCategoryScrolling.this.parent, + Lists.newArrayList(new ConfigElement(Cowlection.getInstance().getConfig().getTabCompletableNamesCommandsProperty())), + Cowlection.MODID, "cowlectionTabCompletableCommands", false, false, + EnumChatFormatting.GOLD + "Press 2x Done to save changes. " + EnumChatFormatting.RED + "Requires a game restart to take effect!")))); + continue; + } else if (type == Property.Type.BOOLEAN) { + this.listEntries.add(new BooleanConfigEntry(configEntry)); + continue; + } else if (type == Property.Type.INTEGER) { + if (configEntry.getLanguageKey() != null && configEntry.getLanguageKey().endsWith("KeyBinding")) { + // special case: key binding + this.listEntries.add(new KeyBindingConfigEntry(configEntry)); + continue; + } else if (configEntry.isIntValue() && configEntry.getMinValue() != null && configEntry.getMaxValue() != null) { + // generic special case: int value with min & max value + this.listEntries.add(new NumberSliderConfigEntry(configEntry, + subCategory.getGuiSliderExtra(configEntry.getLanguageKey()))); + continue; + } + } else if (type == Property.Type.STRING) { + if (configEntry.getLanguageKey().equals(Cowlection.MODID + ".config.isMooValid")) { + // special case: moo! + this.listEntries.add(new BooleanConfigEntry(configEntry)); + continue; + } else if (configEntry.getValidValues() != null && configEntry.getValidValues().length > 0) { + this.listEntries.add(new CycleConfigEntry(configEntry)); + continue; + } + } + // type == Property.Type.DOUBLE + // type == Property.Type.COLOR // => ChatColorEntry#drawEntry + // type == Property.Type.MOD_ID + // + some other cases + throw new NotImplementedException("Unsupported config entry of type " + configEntry.getType() + " (" + configEntry.getName() + ")"); + } + } + } + + protected int getSize() { + return this.listEntries.size(); + } + + /** + * Gets the IGuiListEntry object for the given index + */ + public GuiListExtended.IGuiListEntry getListEntry(int index) { + return this.listEntries.get(index); + } + + @Override + protected void drawSlot(int slotIndex, int x, int y, int slotHeight, int mouseXIn, int mouseYIn) { + if (y >= this.top - 5 && y <= this.bottom) { + // entry is visible + IGuiListEntry listEntry = this.getListEntry(slotIndex); + + MooConfigPreview preview = listEntriesPreviews.get(slotIndex); + boolean enablePreview = true; + if (preview != null) { + // draw preview + if (listEntry instanceof BaseConfigEntry) { + Property property = ((BaseConfigEntry) listEntry).property; + if (property.isBooleanValue()) { + enablePreview = property.getBoolean(); + } + } + preview.drawPreview(x + getListWidth(), y, mouseXIn, mouseYIn, enablePreview); + } + + listEntry.drawEntry(slotIndex, x, y, this.getListWidth(), slotHeight, mouseXIn, mouseYIn, this.getSlotIndexFromScreenCoords(mouseXIn, mouseYIn) == slotIndex); + } + } + + protected int getScrollBarX() { + return this.left + 5; + } + + /** + * Gets the width of the list (label + property element [button/textbox] + undo and reset buttons; without preview) + */ + public int getListWidth() { + return maxListLabelWidth + 135; + } + + private int hoveredSlotIndex = -2; + + @Override + public int getSlotIndexFromScreenCoords(int mouseX, int mouseY) { + int k = mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4; + int slotIndex = k / this.slotHeight; + int result = isMouseYWithinSlotBounds(mouseY) && slotIndex < this.getSize() ? slotIndex : -1; + if (result != hoveredSlotIndex) { + hoveredSlotIndex = result; + } + return result; + } + + @Override + protected void overlayBackground(int startY, int endY, int startAlpha, int endAlpha) { + // no overlay needed; entries are not drawn when they're out of the draw area + } + + @Override + protected void drawContainerBackground(Tessellator tessellator) { + // default behavior: draw dirt background + } + + @Override + public void drawScreen(int mouseX, int mouseY, float p_148128_3_) { + if (this.field_178041_q) { + this.bindAmountScrolled(); + this.mouseX = mouseX; + this.mouseY = mouseY; + this.drawBackground(); + GlStateManager.disableLighting(); + GlStateManager.disableFog(); + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + // Forge: background rendering moved into separate method. + this.drawContainerBackground(tessellator); + int x = this.left + 20; // menu + 20 margin + int y = this.top + 4 - (int) this.amountScrolled; + + if (this.hasListHeader) { + this.drawListHeader(x, y, tessellator); + } + + this.drawSelectionBox(x, y, mouseX, mouseY); + GlStateManager.disableDepth(); + this.overlayBackground(0, this.top, 255, 255); + this.overlayBackground(this.bottom, this.height, 255, 255); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(770, 771, 0, 1); + GlStateManager.disableAlpha(); + GlStateManager.shadeModel(7425); + GlStateManager.disableTexture2D(); + + int maxScroll = this.func_148135_f(); + if (maxScroll > 0) { + // draw scrollbars + int scrollBarX = this.getScrollBarX(); + int scrollBarXEnd = scrollBarX + 6; + + int k1 = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); + k1 = MathHelper.clamp_int(k1, 32, this.bottom - this.top - 8); + int l1 = (int) this.amountScrolled * (this.bottom - this.top - k1) / maxScroll + this.top; + + if (l1 < this.top) { + l1 = this.top; + } + + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + worldrenderer.pos(scrollBarX, this.bottom, 0.0D).tex(0.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd, this.bottom, 0.0D).tex(1.0D, 1.0D).color(0, 0, 0, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd, this.top, 0.0D).tex(1.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + worldrenderer.pos(scrollBarX, this.top, 0.0D).tex(0.0D, 0.0D).color(0, 0, 0, 255).endVertex(); + tessellator.draw(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + worldrenderer.pos(scrollBarX, l1 + k1, 0.0D).tex(0.0D, 1.0D).color(128, 128, 128, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd, l1 + k1, 0.0D).tex(1.0D, 1.0D).color(128, 128, 128, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd, l1, 0.0D).tex(1.0D, 0.0D).color(128, 128, 128, 255).endVertex(); + worldrenderer.pos(scrollBarX, l1, 0.0D).tex(0.0D, 0.0D).color(128, 128, 128, 255).endVertex(); + tessellator.draw(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX_COLOR); + worldrenderer.pos(scrollBarX, l1 + k1 - 1, 0.0D).tex(0.0D, 1.0D).color(192, 192, 192, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd - 1, l1 + k1 - 1, 0.0D).tex(1.0D, 1.0D).color(192, 192, 192, 255).endVertex(); + worldrenderer.pos(scrollBarXEnd - 1, l1, 0.0D).tex(1.0D, 0.0D).color(192, 192, 192, 255).endVertex(); + worldrenderer.pos(scrollBarX, l1, 0.0D).tex(0.0D, 0.0D).color(192, 192, 192, 255).endVertex(); + tessellator.draw(); + } + + this.func_148142_b(mouseX, mouseY); // GuiSlot#renderDecorations + GlStateManager.enableTexture2D(); + GlStateManager.shadeModel(7424); + GlStateManager.enableAlpha(); + GlStateManager.disableBlend(); + + // draw tooltips: + if (hoveredSlotIndex >= 0) { + // draw tooltip for category heading + List<String> explanations = this.explanations.get(hoveredSlotIndex); + MooConfig.Setting configGuiExplanationsDisplay = MooConfig.getConfigGuiExplanationsDisplay(); + if (explanations != null && !explanations.isEmpty() && (configGuiExplanationsDisplay == MooConfig.Setting.TOOLTIP || configGuiExplanationsDisplay == MooConfig.Setting.SPECIAL)) { + if (configGuiExplanationsDisplay == MooConfig.Setting.SPECIAL) { + Gui.drawRect(0, 0, this.right, this.bottom + 20, 0x99111111); + } + GuiHelper.drawHoveringText(explanations, this.getScrollBarX(), mouseY, parent.width, parent.height, parent.width - this.getScrollBarX() - 30); + GlStateManager.disableLighting(); + } + IGuiListEntry hoveredEntry = this.listEntries.get(hoveredSlotIndex); + if (hoveredEntry instanceof BaseConfigEntry) { + // draw tooltip + ((BaseConfigEntry) hoveredEntry).checkHover(mouseX, mouseY); + + MooConfigPreview.drawPreviewHover(mouseX, mouseY); + } + } + } + } + + public void keyTyped(char typedChar, int keyCode) { + if (currentKeyBindingConfigEntry != null) { + // change key binding + if (keyCode == Keyboard.KEY_ESCAPE) { + currentKeyBindingConfigEntry.setKeyBinding(-1); + } else if (keyCode != 0) { + currentKeyBindingConfigEntry.setKeyBinding(keyCode); + } + } else if (this.hoveredSlotIndex >= 0) { + // move GuiSlider by +/- 1 with left/right arrow key: + int direction; + if (keyCode == Keyboard.KEY_LEFT) { + direction = -1; + } else if (keyCode == Keyboard.KEY_RIGHT) { + direction = 1; + } else { + // abort! + return; + } + IGuiListEntry listEntry = this.getListEntry(this.hoveredSlotIndex); + if (listEntry instanceof NumberSliderConfigEntry) { + NumberSliderConfigEntry configEntry = (NumberSliderConfigEntry) listEntry; + if (configEntry.btnChangeConfigEntry.enabled && configEntry.btnChangeConfigEntry instanceof GuiSlider) { + GuiSlider slider = (GuiSlider) configEntry.btnChangeConfigEntry; + slider.setValue(slider.getValue() + direction); + configEntry.updateConfigEntryButtonText(); + } + } + } + } + + @Override + public boolean mouseClicked(int mouseX, int mouseY, int mouseEvent) { + if (currentKeyBindingConfigEntry != null) { + currentKeyBindingConfigEntry.undoChanges(); + } + return super.mouseClicked(mouseX, mouseY, mouseEvent); + } + + public boolean isModifyingKeyBind() { + return currentKeyBindingConfigEntry != null; + } + + @SideOnly(Side.CLIENT) + public class CategoryEntry implements GuiListExtended.IGuiListEntry { + private final String labelText; + private final int labelWidth; + private final boolean hasExplanations; + + public CategoryEntry(String labelText, boolean hasExplanations) { + this.labelText = "" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + I18n.format(labelText); + this.labelWidth = MooConfigCategoryScrolling.this.mc.fontRendererObj.getStringWidth(this.labelText); + this.hasExplanations = hasExplanations; + } + + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected) { + GuiHelper.drawThinHorizontalLine(x, MooConfigCategoryScrolling.this.right, y + (slotHeight - MooConfigCategoryScrolling.this.mc.fontRendererObj.FONT_HEIGHT) / 2, 0x99ffffff); + + int yTextPos = y + slotHeight - MooConfigCategoryScrolling.this.mc.fontRendererObj.FONT_HEIGHT + 3; + if (hasExplanations && MooConfig.getConfigGuiExplanationsDisplay() != MooConfig.Setting.DISABLED) { + // draw "!" to indicate a sub-category has additional info + MooConfigCategoryScrolling.this.mc.fontRendererObj.drawString(EnumChatFormatting.DARK_GREEN + "â˘", x + 2, yTextPos, 0xffffff); + } + // draw sub category label + int labelX = (int) (x + MooConfigCategoryScrolling.this.maxListLabelWidth * 0.75 - this.labelWidth / 2); + MooConfigCategoryScrolling.this.mc.fontRendererObj.drawString(this.labelText, labelX, yTextPos, 0xffffff); + } + + /** + * Returns true if the mouse has been pressed on this control. + */ + public boolean mousePressed(int slotIndex, int p_148278_2_, int p_148278_3_, int p_148278_4_, int p_148278_5_, int p_148278_6_) { + return false; + } + + /** + * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY + */ + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + } + + public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_) { + } + } + + @SideOnly(Side.CLIENT) + public class ExplanationsEntry implements IGuiListEntry { + private final String msgLine; + private final String msgLine2; + + public ExplanationsEntry(String msgLine) { + EnumChatFormatting prefix = EnumChatFormatting.YELLOW; + msgLine = StringUtils.replaceEach(msgLine, + new String[]{EnumChatFormatting.YELLOW.toString(), EnumChatFormatting.RESET.toString()}, + new String[]{EnumChatFormatting.WHITE.toString(), EnumChatFormatting.YELLOW.toString()}); + if (msgLine.contains("\n")) { + String[] msgLines = msgLine.split("\n", 2); + this.msgLine = prefix + msgLines[0]; + this.msgLine2 = prefix + msgLines[1]; + } else { + this.msgLine = prefix + msgLine; + this.msgLine2 = null; + } + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected) { + FontRenderer fontRendererObj = MooConfigCategoryScrolling.this.mc.fontRendererObj; + fontRendererObj.drawString(msgLine, x, y, 16777215); + if (msgLine2 != null) { + fontRendererObj.drawString(msgLine2, x, y + 1 + fontRendererObj.FONT_HEIGHT, 16777215); + } + } + + @Override + public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_) { + } + + + @Override + public boolean mousePressed(int slotIndex, int p_148278_2_, int p_148278_3_, int p_148278_4_, int p_148278_5_, int p_148278_6_) { + return false; + } + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + } + } + + /** + * Based on {@link net.minecraftforge.fml.client.config.GuiConfigEntries.ListEntryBase} and its subclasses + */ + @SideOnly(Side.CLIENT) + public abstract class BaseConfigEntry implements GuiListExtended.IGuiListEntry { + /** + * The property specified for this ConfigEntry + */ + protected final Property property; + protected final GuiButtonExt btnUndoChanges; + protected final GuiButtonExt btnDefault; + protected HoverChecker undoHoverChecker; + protected HoverChecker defaultHoverChecker; + protected final String name; + /** + * The localized key description for this ConfigEntry + */ + private final List<String> tooltip; + + private BaseConfigEntry(Property property) { + this.property = property; + this.name = I18n.format(property.getLanguageKey()); + this.tooltip = Arrays.asList(I18n.format(property.getLanguageKey() + ".tooltip").split("\\\\n")); + + this.btnUndoChanges = new GuiButtonExt(0, 0, 0, 18, 18, UNDO_CHAR); + this.btnDefault = new GuiButtonExt(0, 0, 0, 18, 18, RESET_CHAR); + + this.undoHoverChecker = new HoverChecker(this.btnUndoChanges, 250); + this.defaultHoverChecker = new HoverChecker(this.btnDefault, 500); + } + + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected) { + MooConfigCategoryScrolling.this.mc.fontRendererObj.drawString(this.name, x, y + slotHeight / 2 - MooConfigCategoryScrolling.this.mc.fontRendererObj.FONT_HEIGHT / 2 + 2, 16777215); + + this.btnUndoChanges.xPosition = x + MooConfigCategoryScrolling.this.maxListLabelWidth + 88; + this.btnUndoChanges.yPosition = y + 1; + this.btnUndoChanges.enabled = this.isChanged(); + this.btnUndoChanges.drawButton(MooConfigCategoryScrolling.this.mc, mouseX, mouseY); + + this.btnDefault.xPosition = x + MooConfigCategoryScrolling.this.maxListLabelWidth + 88 + 20; + this.btnDefault.yPosition = y + 1; + this.btnDefault.enabled = !isDefault(); + this.btnDefault.drawButton(MooConfigCategoryScrolling.this.mc, mouseX, mouseY); + + if (mouseX >= x && mouseX < x + maxListLabelWidth && mouseY >= y && mouseY < y + slotHeight) { + // mouse is over entry + GuiHelper.drawHoveringText(Lists.newArrayList(tooltip), mouseX, mouseY, width, height, 300); + GlStateManager.disableLighting(); + } + } + + public void checkHover(int mouseX, int mouseY) { + if (undoHoverChecker != null && undoHoverChecker.checkHover(mouseX, mouseY)) { + GuiHelper.drawHoveringText(Collections.singletonList(I18n.format("fml.configgui.tooltip.undoChanges")), mouseX, mouseY, parent.width, parent.height, 300); + } else if (defaultHoverChecker != null && defaultHoverChecker.checkHover(mouseX, mouseY)) { + GuiHelper.drawHoveringText(Collections.singletonList(I18n.format("fml.configgui.tooltip.resetToDefault")), mouseX, mouseY, parent.width, parent.height, 300); + } + } + + /** + * Returns true if the mouse has been pressed on this control. + */ + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + if (currentKeyBindingConfigEntry != null && currentKeyBindingConfigEntry != this) { + // clicked on another undo/reset button while editing a key binding => abort changing key bind + currentKeyBindingConfigEntry.undoChanges(); + } + if (this.btnDefault.mousePressed(mc, mouseX, mouseY)) { + btnDefault.playPressSound(mc.getSoundHandler()); + setToDefault(); + return true; + } else if (this.btnUndoChanges.mousePressed(mc, mouseX, mouseY)) { + btnUndoChanges.playPressSound(mc.getSoundHandler()); + undoChanges(); + return true; + } + return false; + } + + + /** + * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY + */ + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + this.btnUndoChanges.mouseReleased(x, y); + this.btnDefault.mouseReleased(x, y); + + Cowlection.getInstance().getConfig().syncFromGuiWithoutSaving(); + } + + public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_) { + } + + public boolean isDefault() { + return property.isDefault(); + } + + public abstract boolean isChanged(); + + public void setToDefault() { + property.setToDefault(); + } + + public abstract void undoChanges(); + } + + public abstract class ButtonConfigEntry extends BaseConfigEntry { + protected GuiButton btnChangeConfigEntry; + + private ButtonConfigEntry(Property property) { + super(property); + this.btnChangeConfigEntry = new GuiButton(0, 0, 0, 78, 20, "Loading..."); + + if (property.getLanguageKey().equals(Cowlection.MODID + ".config.isMooValid")) { + btnChangeConfigEntry.enabled = false; + btnUndoChanges.enabled = false; + btnUndoChanges.visible = false; + btnDefault.enabled = false; + btnDefault.visible = false; + defaultHoverChecker = null; + undoHoverChecker = null; + } + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected) { + this.btnChangeConfigEntry.xPosition = x + MooConfigCategoryScrolling.this.maxListLabelWidth + 5; + this.btnChangeConfigEntry.yPosition = y; + this.btnChangeConfigEntry.drawButton(MooConfigCategoryScrolling.this.mc, mouseX, mouseY); + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected); + } + + @Override + public boolean mousePressed(int slotIndex, int mouseX, int mouseY, int mouseEvent, int relativeX, int relativeY) { + if (this.btnChangeConfigEntry.mousePressed(mc, mouseX, mouseY)) { + btnChangeConfigEntry.playPressSound(mc.getSoundHandler()); + changeConfigEntryButtonPressed(); + return true; + } else { + return super.mousePressed(slotIndex, mouseX, mouseY, mouseEvent, relativeX, relativeY); + } + } + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + super.mouseReleased(slotIndex, x, y, mouseEvent, relativeX, relativeY); + this.btnChangeConfigEntry.mouseReleased(x, y); + } + + protected abstract void changeConfigEntryButtonPressed(); + + @SuppressWarnings("unused") + protected abstract void updateConfigEntryButtonText(); + } + + private class BooleanConfigEntry extends ButtonConfigEntry { + protected final boolean beforeValue; + + public BooleanConfigEntry(Property property) { + super(property); + beforeValue = property.getBoolean(); + + updateConfigEntryButtonText(); + } + + @Override + protected void changeConfigEntryButtonPressed() { + property.set(!property.getBoolean()); + updateConfigEntryButtonText(); + } + + @Override + public boolean isChanged() { + return property.getBoolean() != beforeValue; + } + + @Override + public void setToDefault() { + super.setToDefault(); + updateConfigEntryButtonText(); + } + + @Override + public void undoChanges() { + property.set(beforeValue); + updateConfigEntryButtonText(); + } + + @Override + protected void updateConfigEntryButtonText() { + btnChangeConfigEntry.displayString = Utils.booleanToSymbol(property.getBoolean()); + } + } + + private class GuiSwitchEntry extends ButtonConfigEntry { + private final Runnable runnable; + + private GuiSwitchEntry(String langKey, String buttonText, Runnable runnable) { + super(new Property("fakeProperty", "fakeValue", Property.Type.STRING, Cowlection.MODID + ".config." + langKey)); + btnChangeConfigEntry.displayString = buttonText; + this.runnable = runnable; + + this.btnDefault.visible = false; + this.btnUndoChanges.visible = false; + } + + @Override + public boolean isChanged() { + return false; + } + + @Override + public void undoChanges() { + } + + @Override + protected void changeConfigEntryButtonPressed() { + runnable.run(); + } + + @Override + protected void updateConfigEntryButtonText() { + } + } + + private class CycleConfigEntry extends ButtonConfigEntry { + protected final int beforeIndex; + protected final int defaultIndex; + protected int currentIndex; + /** + * Work-around to avoid infinite loop: updateConfigEntryButtonText <> switchDisplayedConfigCategory + */ + private boolean hasBeenModified = false; + + private CycleConfigEntry(Property property) { + super(property); + beforeIndex = getIndex(property.getString()); + defaultIndex = getIndex(property.getDefault()); + currentIndex = beforeIndex; + updateConfigEntryButtonText(); + } + + private int getIndex(String s) { + for (int i = 0; i < property.getValidValues().length; i++) { + if (property.getValidValues()[i].equals(s)) { + return i; + } + } + return 0; + } + + @Override + public boolean isDefault() { + return currentIndex == defaultIndex; + } + + @Override + public boolean isChanged() { + return currentIndex != beforeIndex; + } + + @Override + public void setToDefault() { + currentIndex = defaultIndex; + updateConfigEntryButtonText(); + } + + @Override + public void undoChanges() { + currentIndex = beforeIndex; + updateConfigEntryButtonText(); + } + + @Override + protected void changeConfigEntryButtonPressed() { + if (++this.currentIndex >= property.getValidValues().length) { + this.currentIndex = 0; + } + + updateConfigEntryButtonText(); + } + + @Override + protected void updateConfigEntryButtonText() { + String newValue = property.getValidValues()[currentIndex]; + property.setValue(newValue); + this.btnChangeConfigEntry.displayString = newValue; + + if (hasBeenModified && (Cowlection.MODID + ".config.configGuiExplanations").equals(property.getLanguageKey())) { + // save properties and re-draw category + Cowlection.getInstance().getConfig().syncFromGuiWithoutSaving(); + parent.switchDisplayedConfigCategory(); + } + hasBeenModified = true; + } + } + + private class NumberSliderConfigEntry extends ButtonConfigEntry { + protected final int beforeValue; + + private NumberSliderConfigEntry(Property property, MooConfigCategory.SubCategory.GuiSliderExtra guiSliderExtra) { + super(property); + + beforeValue = property.getInt(); + + int minVal = Integer.parseInt(property.getMinValue()); + int maxVal = Integer.parseInt(property.getMaxValue()); + + String prefix = ""; + String suffix = ""; + GuiSlider.ISlider onChangeSliderValue = null; + if (guiSliderExtra != null) { + onChangeSliderValue = guiSliderExtra.getOnChangeSliderValue(); + prefix = guiSliderExtra.getPrefix(); + suffix = guiSliderExtra.getSuffix(); + } + + this.btnChangeConfigEntry = new GuiSlider(0, 0, 0, 78, 20, prefix, suffix, minVal, maxVal, beforeValue, false, true, onChangeSliderValue); + updateConfigEntryButtonText(); + MooConfigGui.showDungeonPerformanceOverlayUntil = 0; + } + + @Override + public boolean isDefault() { + return ((GuiSlider) this.btnChangeConfigEntry).getValueInt() == Integer.parseInt(property.getDefault()); + } + + @Override + public boolean isChanged() { + return ((GuiSlider) this.btnChangeConfigEntry).getValueInt() != beforeValue; + } + + @Override + public void setToDefault() { + ((GuiSlider) this.btnChangeConfigEntry).setValue(Double.parseDouble(property.getDefault())); + updateConfigEntryButtonText(); + } + + @Override + public void undoChanges() { + ((GuiSlider) this.btnChangeConfigEntry).setValue(beforeValue); + updateConfigEntryButtonText(); + } + + @Override + protected void changeConfigEntryButtonPressed() { + } + + @Override + public void mouseReleased(int slotIndex, int x, int y, int mouseEvent, int relativeX, int relativeY) { + if (((GuiSlider) btnChangeConfigEntry).dragging) { + updateConfigEntryButtonText(); + } + super.mouseReleased(slotIndex, x, y, mouseEvent, relativeX, relativeY); + } + + @Override + protected void updateConfigEntryButtonText() { + property.setValue(((GuiSlider) this.btnChangeConfigEntry).getValueInt()); + + ((GuiSlider) this.btnChangeConfigEntry).updateSlider(); + } + } + + private class KeyBindingConfigEntry extends ButtonConfigEntry { + private final int beforeValue; + + public KeyBindingConfigEntry(Property configEntry) { + super(configEntry); + beforeValue = property.getInt(); + + updateConfigEntryButtonText(); + } + + @Override + protected void changeConfigEntryButtonPressed() { + currentKeyBindingConfigEntry = this; + updateConfigEntryButtonText(); + } + + @Override + protected void updateConfigEntryButtonText() { + int keyCode = property.getInt(); + String keyName = keyCode > 1 && keyCode < 256 ? Keyboard.getKeyName(keyCode) : EnumChatFormatting.ITALIC + "none"; + if (currentKeyBindingConfigEntry == this) { + // key is is currently being modified + keyName = EnumChatFormatting.WHITE + "> " + EnumChatFormatting.YELLOW + (keyName != null ? keyName : "Key #" + keyCode) + EnumChatFormatting.WHITE + " <"; + } + btnChangeConfigEntry.displayString = keyName; + } + + @Override + public boolean isChanged() { + return property.getInt() != beforeValue; + } + + @Override + public void undoChanges() { + property.set(beforeValue); + currentKeyBindingConfigEntry = null; + Cowlection.getInstance().getConfig().syncFromGuiWithoutSaving(); + updateConfigEntryButtonText(); + } + + @Override + public void setToDefault() { + super.setToDefault(); + currentKeyBindingConfigEntry = null; + Cowlection.getInstance().getConfig().syncFromGuiWithoutSaving(); + updateConfigEntryButtonText(); + } + + public void setKeyBinding(int keyCode) { + property.set(Math.min(keyCode, 255)); + currentKeyBindingConfigEntry = null; + Cowlection.getInstance().getConfig().syncFromGuiWithoutSaving(); + updateConfigEntryButtonText(); + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigGui.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigGui.java new file mode 100644 index 0000000..e9af52c --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigGui.java @@ -0,0 +1,206 @@ +package de.cowtipper.cowlection.config.gui; + +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.config.MooConfigCategory; +import de.cowtipper.cowlection.listener.PlayerListener; +import de.cowtipper.cowlection.util.GuiHelper; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import org.lwjgl.input.Keyboard; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Main config gui containing: + * <ul> + * <li>menu ({@link MooConfigMenuList}) with list of config categories ({@link MooConfigCategory})</li> + * <li>the current opened config category with its sub-categories ({@link MooConfigCategoryScrolling})</li> + * </ul> + * Based on {@link net.minecraft.client.gui.GuiControls} + */ +public class MooConfigGui extends GuiScreen { + public static long showDungeonPerformanceOverlayUntil; + public int menuWidth; + private MooConfigMenuList menu; + private int selectedMenuIndex = -1; + private MooConfigCategory currentConfigCategory; + private final boolean isOutsideOfSkyBlock; + /** + * equivalent of GuiModList.keyBindingList + */ + private MooConfigCategoryScrolling currentConfigCategoryGui; + private GuiButton btnClose; + + public MooConfigGui() { + isOutsideOfSkyBlock = PlayerListener.registerSkyBlockListeners(); + } + + /** + * Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the + * window resizes, the buttonList is cleared beforehand. + */ + @Override + public void initGui() { + Keyboard.enableRepeatEvents(true); + MooConfigPreview.parent = this; + + // re-register SkyBlock listeners if necessary (mainly so that previews function correctly outside of SkyBlock) + PlayerListener.registerSkyBlockListeners(); + + for (MooConfigCategory configCategory : MooConfig.getConfigCategories()) { + menuWidth = Math.max(menuWidth, fontRendererObj.getStringWidth(configCategory.getMenuDisplayName()) + 10 + 2); + } + menuWidth = Math.min(menuWidth, 150); + this.menu = new MooConfigMenuList(this, menuWidth); + + this.buttonList.add(this.btnClose = new GuiButton(6, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); + + if (selectedMenuIndex < 0) { + // switch to 1st category if none is selected + selectConfigCategory(0); + } + } + + @Override + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + if (currentConfigCategoryGui != null) { + this.currentConfigCategoryGui.handleMouseInput(); + } + } + + /** + * Called by the controls from the buttonList when activated. (Mouse pressed for buttons) + */ + @Override + protected void actionPerformed(GuiButton button) throws IOException { + if (button.enabled) { + if (button.id == 6) { // close gui + this.mc.displayGuiScreen(null); + } + } + } + + /** + * Called when the mouse is clicked. Args : mouseX, mouseY, clickedButton + */ + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + if (mouseButton != 0 || (currentConfigCategoryGui != null && !this.currentConfigCategoryGui.mouseClicked(mouseX, mouseY, mouseButton))) { + super.mouseClicked(mouseX, mouseY, mouseButton); + } + } + + /** + * Called when a mouse button is released. Args : mouseX, mouseY, releaseButton + */ + protected void mouseReleased(int mouseX, int mouseY, int state) { + if (state != 0 || currentConfigCategoryGui != null && !this.currentConfigCategoryGui.mouseReleased(mouseX, mouseY, state)) { + super.mouseReleased(mouseX, mouseY, state); + } + } + + /** + * Fired when a key is typed (except F11 which toggles full screen). This is the equivalent of + * KeyListener.keyTyped(KeyEvent e). Args : character (character on the key), keyCode (lwjgl Keyboard key code) + */ + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + if (keyCode == Keyboard.KEY_ESCAPE && (currentConfigCategoryGui == null || !currentConfigCategoryGui.isModifyingKeyBind())) { + super.keyTyped(typedChar, keyCode); + } else if (this.currentConfigCategoryGui != null) { + this.currentConfigCategoryGui.keyTyped(typedChar, keyCode); + } + } + + /** + * Draws the screen and all the components in it. Args : mouseX, mouseY, renderPartialTicks + */ + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + if (!showDungeonPerformanceOverlay()) { + this.menu.drawScreen(mouseX, mouseY, partialTicks); + } + + String guiTitle = "" + EnumChatFormatting.BOLD + EnumChatFormatting.UNDERLINE + Cowlection.MODNAME + " config" + (currentConfigCategory != null ? ":" + EnumChatFormatting.RESET + " " + currentConfigCategory.getDisplayName() : ""); + int guiTitleX = ((menu.getRight() + this.width) / 2) - this.fontRendererObj.getStringWidth(guiTitle) / 2; + this.drawCenteredString(this.fontRendererObj, guiTitle, guiTitleX, 16, 0xFFFFFF); + super.drawScreen(mouseX, mouseY, partialTicks); + + if (currentConfigCategoryGui != null) { + currentConfigCategoryGui.drawScreen(mouseX, mouseY, partialTicks); + } + if (btnClose.isMouseOver()) { + GuiHelper.drawHoveringText(Arrays.asList(EnumChatFormatting.RED + "Save & close settings", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC"), mouseX, mouseY, width, height, 300); + } + } + + @Override + public void drawDefaultBackground() { + if (!MooConfigGui.showDungeonPerformanceOverlay()) { + super.drawDefaultBackground(); + } + } + + // config category menu methods: + + /** + * Select a config category via the menu + */ + public void selectConfigCategory(int index) { + if (index == this.selectedMenuIndex) { + return; + } + this.selectedMenuIndex = index; + this.currentConfigCategory = (index >= 0 && index <= MooConfig.getConfigCategories().size()) ? MooConfig.getConfigCategories().get(selectedMenuIndex) : null; + + switchDisplayedConfigCategory(); + Cowlection.getInstance().getConfig().syncFromGui(); + } + + /** + * Helper method for menu: is config category selected? + */ + public boolean isConfigCategorySelected(int index) { + return index == selectedMenuIndex; + } + + public void switchDisplayedConfigCategory() { + if (currentConfigCategory == null) { + return; + } + currentConfigCategoryGui = new MooConfigCategoryScrolling(this, mc, currentConfigCategory, menu.getRight() + 3); + } + + @Override + public void renderToolTip(ItemStack stack, int x, int y) { + super.renderToolTip(stack, x, y); + } + + @Override + public void handleComponentHover(IChatComponent component, int x, int y) { + super.handleComponentHover(component, x, y); + } + + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + Cowlection.getInstance().getConfig().syncFromGui(); + + if (isOutsideOfSkyBlock) { + PlayerListener.unregisterSkyBlockListeners(); + } + if (MooConfigPreview.parent != null) { + MooConfigPreview.parent = null; + } + } + + public static boolean showDungeonPerformanceOverlay() { + return showDungeonPerformanceOverlayUntil > System.currentTimeMillis(); + } +} diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java new file mode 100644 index 0000000..310e09e --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java @@ -0,0 +1,70 @@ +package de.cowtipper.cowlection.config.gui; + +import de.cowtipper.cowlection.config.MooConfig; +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.minecraftforge.fml.client.GuiScrollingList; + +/** + * Config menu displaying a list of config categories + * <p> + * Based on {@link net.minecraftforge.fml.client.GuiSlotModList} + */ +public class MooConfigMenuList extends GuiScrollingList { + private final MooConfigGui parent; + + public MooConfigMenuList(MooConfigGui parent, int listWidth) { + super(Minecraft.getMinecraft(), listWidth, parent.height, 32, parent.height - 5, 5, 15, parent.width, parent.height); + this.parent = parent; + } + + @Override + protected int getSize() { + return MooConfig.getConfigCategories().size(); + } + + @Override + protected void elementClicked(int index, boolean doubleClick) { + this.parent.selectConfigCategory(index); + } + + @Override + protected boolean isSelected(int index) { + return this.parent.isConfigCategorySelected(index); + } + + @Override + protected void drawBackground() { + this.parent.drawDefaultBackground(); + } + + @Override + protected int getContentHeight() { + return (this.getSize()) * 15 + 1; + } + + @Override + protected void drawScreen(int mouseX, int mouseY) { + super.drawScreen(mouseX, mouseY); + } + + @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()); + FontRenderer font = Minecraft.getMinecraft().fontRendererObj; + + font.drawString(font.trimStringToWidth(name, listWidth - 10), this.left + 3, top + 2, 0xFFFFFF); + } + + protected int getRight() { + return this.right; + } + + public int getLeft() { + return this.left; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java new file mode 100644 index 0000000..3499fdd --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java @@ -0,0 +1,175 @@ +package de.cowtipper.cowlection.config.gui; + +import de.cowtipper.cowlection.util.MooChatComponent; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiUtilRenderComponents; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.client.config.GuiUtils; +import org.apache.commons.lang3.text.WordUtils; + +import java.util.HashMap; +import java.util.Map; + +public class MooConfigPreview { + private final Type type; + private ItemStack[] items; + private IChatComponent chatComponent; + public static ItemStack hoveredItem; + public static IChatComponent hoveredChatComponent; + public static MooConfigGui parent; + + public MooConfigPreview(IChatComponent chatComponent) { + this.type = Type.CHAT; + this.chatComponent = chatComponent; + } + + public MooConfigPreview(ItemStack... items) { + this.type = Type.ITEM; + this.items = items; + } + + public void drawPreview(int x, int y, int mouseX, int mouseY, boolean enablePreview) { + switch (type) { + case ITEM: + drawItemsPreview(x, y, mouseX, mouseY, enablePreview); + break; + case CHAT: + drawChatPreview(x, y, mouseX, mouseY, enablePreview); + } + } + + public static void drawPreviewHover(int mouseX, int mouseY) { + if (hoveredItem != null) { + // draw preview item tool tip + parent.renderToolTip(hoveredItem, mouseX, mouseY); + hoveredItem = null; + } else if (hoveredChatComponent != null) { + // draw hover event of hovered chat component + parent.handleComponentHover(hoveredChatComponent, mouseX, mouseY); + hoveredChatComponent = null; + } + } + + private void drawItemsPreview(int x, int yFakeHotbar, int mouseX, int mouseY, boolean enablePreview) { + int xFakeHotbar = x + 15; + GlStateManager.pushMatrix(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("textures/gui/container/inventory.png")); + + GuiUtils.drawTexturedModalRect(xFakeHotbar, yFakeHotbar, 87, 25, 18 * items.length, 18, 0); + + GlStateManager.enableRescaleNormal(); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + RenderHelper.enableGUIStandardItemLighting(); + + int xItem = xFakeHotbar + 1; + int yItem = yFakeHotbar + 1; + for (ItemStack item : items) { + Minecraft.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(item, xItem, yItem); + + // check mouse hover to draw item tooltip + if (enablePreview && mouseX >= xItem - 1 && mouseX < xItem + 18 - 1 && mouseY >= yItem - 1 && mouseY < yItem + 18 - 1) { + hoveredItem = item; + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + GlStateManager.colorMask(true, true, true, false); + // draw white slot hover + Gui.drawRect(xItem, yItem, xItem + 16, yItem + 16, 0x80FFFFFF); + GlStateManager.colorMask(true, true, true, true); + GlStateManager.enableDepth(); + GlStateManager.enableLighting(); + } + xItem += 18; + } + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + + public static ItemStack createDemoItem(String itemId, String itemName, String[] lore, Map<String, NBTBase> extraAttributes) { + return createDemoItem(itemId, itemName, lore, extraAttributes, -1); + } + + public static ItemStack createDemoItem(String itemId, String itemName, String[] lore, Map<String, NBTBase> extraAttributes, int color) { + ItemStack demoItem = new ItemStack(Item.getByNameOrId(itemId)); + + // lore + NBTTagCompound display = new NBTTagCompound(); + demoItem.setTagInfo("display", display); + NBTTagList loreTag = new NBTTagList(); + display.setTag("Lore", loreTag); + for (String loreEntry : lore) { + loreTag.appendTag(new NBTTagString(loreEntry)); + } + // color + if (color >= 0) { + display.setInteger("color", color); + } + // SkyBlock extra attributes + NBTTagCompound extraAttributesTag = new NBTTagCompound(); + demoItem.setTagInfo("ExtraAttributes", extraAttributesTag); + for (Map.Entry<String, NBTBase> extraAttribute : extraAttributes.entrySet()) { + extraAttributesTag.setTag(extraAttribute.getKey(), extraAttribute.getValue()); + } + demoItem.setStackDisplayName(itemName); + + return demoItem; + } + + public static ItemStack createDungeonItem(String modifier, String timestamp, String... lore) { + Map<String, NBTBase> extraAttributes = new HashMap<>(); + extraAttributes.put("modifier", new NBTTagString(modifier)); + extraAttributes.put("baseStatBoostPercentage", new NBTTagInt(48)); + extraAttributes.put("item_tier", new NBTTagInt(1)); + extraAttributes.put("id", new NBTTagString("SKELETON_SOLDIER_LEGGINGS")); + extraAttributes.put("timestamp", new NBTTagString(timestamp)); + + + return createDemoItem("leather_leggings", EnumChatFormatting.DARK_PURPLE + WordUtils.capitalize(modifier) + " Skeleton Soldier Leggings", + lore, extraAttributes, 16759819); + } + + private void drawChatPreview(int x, int y, int mouseX, int mouseY, boolean enablePreview) { + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; + int currentX = x + 15; + int chatY = y + 20 / 2 - fontRenderer.FONT_HEIGHT / 2; + + fontRenderer.drawStringWithShadow(this.chatComponent.getFormattedText(), currentX, chatY, 0xffffffff); + if (!enablePreview) { + Gui.drawRect(currentX - 2, chatY - 2, currentX + fontRenderer.getStringWidth(chatComponent.getUnformattedText()) + 1, chatY + fontRenderer.FONT_HEIGHT + 1, 0xdd444444); + } + + // hover checker for online best friends (partially taken from GuiNewChat#getChatComponent) + if (enablePreview && mouseY >= chatY && mouseY <= chatY + fontRenderer.FONT_HEIGHT) { + for (IChatComponent chatComponent : chatComponent) { + if (chatComponent instanceof ChatComponentText) { + currentX += fontRenderer.getStringWidth(GuiUtilRenderComponents.func_178909_a(chatComponent.getUnformattedTextForChat(), false)); + if (currentX > mouseX) { + hoveredChatComponent = chatComponent; + break; + } + } + } + } + } + + public static IChatComponent createDemoOnline(String name, String gameMode, String onlineTime) { + return new MooChatComponent(name).darkGreen().setHover(new MooChatComponent(gameMode).yellow().appendFreshSibling(new MooChatComponent("Online for " + onlineTime).white())); + } + + private enum Type { + CHAT, ITEM + } +} diff --git a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java index 9af613f..37dd4a3 100644 --- a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java +++ b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java @@ -91,6 +91,28 @@ public final class DataHelper { } } + public enum DungeonClass { + ARCHER('A'), BERSERK('B'), HEALER('H'), MAGE('M'), TANK('T'), UNKNOWN('U'); + private final char shortName; + + DungeonClass(char shortName) { + this.shortName = shortName; + } + + public static DungeonClass get(String className) { + try { + return valueOf(className.toUpperCase()); + } catch (IllegalArgumentException e) { + // invalid class name + return UNKNOWN; + } + } + + public char getShortName() { + return shortName; + } + } + public static Map<String, String> getMinions() { // key = skin id, value = minion type and tier Map<String, String> minions = new HashMap<>(); diff --git a/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java b/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java new file mode 100644 index 0000000..96b391a --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java @@ -0,0 +1,20 @@ +package de.cowtipper.cowlection.data; + +public class HyApiKey { + private boolean success; + private String cause; + + /** + * No-args constructor for GSON + */ + private HyApiKey() { + } + + public boolean isSuccess() { + return success; + } + + public String getCause() { + return cause; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java b/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java index bb4d916..c2c6837 100644 --- a/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java +++ b/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java @@ -118,7 +118,7 @@ public class FriendsHandler { if (!bestFriend.equals(Friend.FRIEND_NOT_FOUND)) { bestFriend.setLastChecked(System.currentTimeMillis()); if (isCommandTriggered) { - throw new MooCommandException(friend.getName() + " hasn't changed his name"); + throw new MooCommandException(friend.getName() + " hasn't changed their name"); } } } else { @@ -202,8 +202,8 @@ public class FriendsHandler { }); } } else { - new TickDelay(() -> main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Couldn't check best friends online status because it has not been long enough since the last check. Next check via " + EnumChatFormatting.WHITE + "/moo online" + EnumChatFormatting.RED + " available in " + DurationFormatUtils.formatDurationWords(nextBestFriendOnlineCheck - System.currentTimeMillis(), true, true)) - , isCommandTriggered ? 1 : 100); + new TickDelay(() -> main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Couldn't check best friends online status because it has not been long enough since the last check. Next check via " + EnumChatFormatting.WHITE + "/moo online" + EnumChatFormatting.RED + " available in " + DurationFormatUtils.formatDurationWords(nextBestFriendOnlineCheck - System.currentTimeMillis(), true, true)), + isCommandTriggered ? 1 : 100); } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java index a936888..e2d2316 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java @@ -167,7 +167,7 @@ public class ChatListener { messageSender = partyOrGameInviteMatcher.group(1); } else if (dungeonPartyFinderJoinedMatcher.find()) { messageSender = dungeonPartyFinderJoinedMatcher.group(1); - if (!"disabled".equals(MooConfig.dungPartyFinderArmorLookup) && !messageSender.equals(Minecraft.getMinecraft().thePlayer.getName())) { + if (MooConfig.getDungPartyFinderArmorLookupDisplay() != MooConfig.Setting.DISABLED && !messageSender.equals(Minecraft.getMinecraft().thePlayer.getName())) { String dungeonClass = dungeonPartyFinderJoinedMatcher.group(2) + " Lvl " + dungeonPartyFinderJoinedMatcher.group(3); getDungeonPartyMemberDetails(messageSender, dungeonClass); } @@ -192,16 +192,17 @@ public class ChatListener { HySkyBlockStats.Profile.Member member = activeProfile.getMember(stalkedPlayer.getUuid()); MooChatComponent armorLookupComponent; String armorLookupPrefix = " â " + EnumChatFormatting.DARK_GREEN + playerName; - String delimiter = "\n" + (MooConfig.showArmorLookupInChat() ? " " : ""); + MooConfig.Setting dungPartyFinderArmorLookupDisplay = MooConfig.getDungPartyFinderArmorLookupDisplay(); + String delimiter = "\n" + (dungPartyFinderArmorLookupDisplay == MooConfig.Setting.TEXT ? " " : ""); String armorLookupResult = EnumChatFormatting.LIGHT_PURPLE + " â " + EnumChatFormatting.GRAY + dungeonClass + delimiter + String.join(delimiter, member.getArmor()); - if (MooConfig.showArmorLookupInChat()) { + if (dungPartyFinderArmorLookupDisplay == MooConfig.Setting.TEXT) { armorLookupComponent = new MooChatComponent(armorLookupPrefix + armorLookupResult).green(); } else { - // as a tooltip + // as a tooltip: == MooConfig.Setting.TOOLTIP armorLookupComponent = new MooChatComponent(armorLookupPrefix + EnumChatFormatting.GREEN + (playerName.endsWith("s") ? "" : "'s") + " armor (hover me)").green() .setHover(new MooChatComponent(EnumChatFormatting.BOLD + playerName + armorLookupResult)); } - main.getChatHelper().sendMessage(armorLookupComponent.setSuggestCommand("/p kick " + playerName, MooConfig.showArmorLookupInChat())); + main.getChatHelper().sendMessage(armorLookupComponent.setSuggestCommand("/p kick " + playerName, dungPartyFinderArmorLookupDisplay == MooConfig.Setting.TEXT)); } }); } diff --git a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java index a990ad4..0925d38 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java @@ -1,6 +1,7 @@ package de.cowtipper.cowlection.listener; import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.event.ApiErrorEvent; import de.cowtipper.cowlection.listener.skyblock.DungeonsListener; @@ -31,8 +32,8 @@ import org.lwjgl.input.Keyboard; public class PlayerListener { private final Cowlection main; - private DungeonsListener dungeonsListener; - private SkyBlockListener skyBlockListener; + private static DungeonsListener dungeonsListener; + private static SkyBlockListener skyBlockListener; private boolean isOnSkyBlock; public PlayerListener(Cowlection main) { @@ -90,7 +91,7 @@ public class PlayerListener { public void onServerJoin(FMLNetworkEvent.ClientConnectedToServerEvent e) { main.getVersionChecker().runUpdateCheck(false); new TickDelay(() -> main.getChatHelper().sendOfflineMessages(), 6 * 20); - if (MooConfig.doBestFriendsOnlineCheck && main.getFriendsHandler().getBestFriends().size() > 0) { + if (MooConfig.doBestFriendsOnlineCheck && CredentialStorage.isMooValid && main.getFriendsHandler().getBestFriends().size() > 0) { main.getFriendsHandler().runBestFriendsOnlineCheck(false); } isOnSkyBlock = false; @@ -122,25 +123,23 @@ public class PlayerListener { }, 40); // 2 second delay, making sure scoreboard got sent } - private void registerSkyBlockListeners() { - if (dungeonsListener == null) { - MinecraftForge.EVENT_BUS.register(dungeonsListener = new DungeonsListener(main)); - } - if (skyBlockListener == null) { - MinecraftForge.EVENT_BUS.register(skyBlockListener = new SkyBlockListener(main)); + public static boolean registerSkyBlockListeners() { + if (dungeonsListener == null && skyBlockListener == null) { + MinecraftForge.EVENT_BUS.register(dungeonsListener = new DungeonsListener(Cowlection.getInstance())); + MinecraftForge.EVENT_BUS.register(skyBlockListener = new SkyBlockListener(Cowlection.getInstance())); + return true; } + return false; } - private void unregisterSkyBlockListeners() { - main.getDungeonCache().onDungeonLeft(); - if (dungeonsListener != null) { + public static void unregisterSkyBlockListeners() { + Cowlection.getInstance().getDungeonCache().onDungeonLeft(); + if (dungeonsListener != null && skyBlockListener != null) { MinecraftForge.EVENT_BUS.unregister(dungeonsListener); dungeonsListener = null; - } - if (skyBlockListener != null) { MinecraftForge.EVENT_BUS.unregister(skyBlockListener); skyBlockListener = null; - main.getLogger().info("Left SkyBlock"); + Cowlection.getInstance().getLogger().info("Left SkyBlock"); } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java index b6bb2d0..1098f1e 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java @@ -1,8 +1,9 @@ package de.cowtipper.cowlection.listener.skyblock; import de.cowtipper.cowlection.Cowlection; -import de.cowtipper.cowlection.config.DungeonOverlayGuiConfig; import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.config.gui.MooConfigGui; +import de.cowtipper.cowlection.data.DataHelper.DungeonClass; import de.cowtipper.cowlection.handler.DungeonCache; import de.cowtipper.cowlection.util.TickDelay; import net.minecraft.client.Minecraft; @@ -10,9 +11,13 @@ import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; import net.minecraft.inventory.Container; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; +import net.minecraft.item.EnumDyeColor; import net.minecraft.item.ItemSkull; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -33,12 +38,12 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.relauncher.Side; import org.apache.commons.lang3.StringUtils; -import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import java.awt.*; import java.util.List; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,11 +100,11 @@ public class DungeonsListener { */ private final Pattern DUNGEON_CLASS_MILESTONE_PATTERN = Pattern.compile("^[A-Za-z]+ Milestone (.): "); - private String activeDungeonClass; + private DungeonClass activeDungeonClass; public DungeonsListener(Cowlection main) { this.main = main; - activeDungeonClass = "unknown"; + activeDungeonClass = DungeonClass.UNKNOWN; } @SubscribeEvent(priority = EventPriority.HIGH) @@ -107,7 +112,7 @@ public class DungeonsListener { if (e.itemStack == null || e.toolTip == null) { return; } - if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) && isDungeonItem(e.toolTip)) { + if (MooConfig.isDungeonItemTooltipToggleKeyBindingPressed() && isDungeonItem(e.toolTip)) { // simplify dungeon armor stats String originalItemName = e.itemStack.getDisplayName(); NBTTagCompound extraAttributes = e.itemStack.getSubCompound("ExtraAttributes", false); @@ -275,8 +280,9 @@ public class DungeonsListener { for (String toolTipLine : dungeonClassIndicator.getTooltip(Minecraft.getMinecraft().thePlayer, false)) { String line = EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine); if (line.startsWith("Currently Selected: ")) { - String selectedClass = line.substring(line.lastIndexOf(' ') + 1); - if (!selectedClass.equals(activeDungeonClass)) { + String selectedClassName = line.substring(line.lastIndexOf(' ') + 1); + DungeonClass selectedClass = DungeonClass.get(selectedClassName); + if (!selectedClass.equals(activeDungeonClass) || selectedClass == DungeonClass.UNKNOWN) { activeDungeonClass = selectedClass; } } @@ -289,11 +295,7 @@ public class DungeonsListener { int inventoryRows = inventory.getSizeInventory() / 9; int ySize = 222 - 108 + inventoryRows * 18; int guiTop = (guiChest.height - ySize) / 2; - GlStateManager.pushMatrix(); - GlStateManager.translate(0, 0, 280); - float scaleFactor = 0.8f; - GlStateManager.scale(scaleFactor, scaleFactor, 0); for (Slot inventorySlot : inventorySlots.inventorySlots) { if (inventorySlot.getHasStack()) { int slotRow = inventorySlot.slotNumber / 9; @@ -301,13 +303,12 @@ public class DungeonsListener { // check if slot is one of the middle slots with parties int maxRow = inventoryRows - 2; if (slotRow > 0 && slotRow < maxRow && slotColumn > 0 && slotColumn < 8) { - int slotX = (int) ((guiLeft + inventorySlot.xDisplayPosition) / scaleFactor); - int slotY = (int) ((guiTop + inventorySlot.yDisplayPosition) / scaleFactor); + int slotX = guiLeft + inventorySlot.xDisplayPosition; + int slotY = guiTop + inventorySlot.yDisplayPosition; renderPartyStatus(inventorySlot.getStack(), slotX, slotY); } } } - GlStateManager.popMatrix(); } } } @@ -317,51 +318,76 @@ public class DungeonsListener { // not a player skull, don't draw party status indicator return; } - String status = "âŹ"; // ok - Color color = new Color(20, 200, 20, 255); + ItemStack indicatorItem = null; List<String> itemTooltip = item.getTooltip(Minecraft.getMinecraft().thePlayer, false); if (itemTooltip.size() < 5) { // not a valid dungeon party tooltip return; } - if (itemTooltip.get(itemTooltip.size() - 1).endsWith("Complete previous floor first!")) { + String lastToolTipLine = EnumChatFormatting.getTextWithoutFormattingCodes(itemTooltip.get(itemTooltip.size() - 1)); + if (lastToolTipLine.endsWith("Complete previous floor first!") + || lastToolTipLine.startsWith("Requires a Class at Level") + || lastToolTipLine.startsWith("Requires Catacombs Level")) { // cannot enter dungeon - status = "â"; - color = new Color(220, 20, 20, 255); - } else if (itemTooltip.get(itemTooltip.size() - 1).endsWith("You are in this party!")) { - status = EnumChatFormatting.OBFUSCATED + "#"; + indicatorItem = new ItemStack(Blocks.carpet, 1, EnumDyeColor.RED.getMetadata()); + } else if (lastToolTipLine.endsWith("You are in this party!")) { + indicatorItem = new ItemStack(Items.leather, 1); } else { - int dungClassMin = MooConfig.dungClassRange[0]; - int dungClassMax = MooConfig.dungClassRange[1]; - Set<String> dungClassesInParty = new HashSet<>(); - dungClassesInParty.add(activeDungeonClass); // add our own class + Map<DungeonClass, AtomicInteger> dungClassesInParty = new LinkedHashMap<>(); + AtomicInteger classCounter = new AtomicInteger(); + classCounter.incrementAndGet(); + dungClassesInParty.put(activeDungeonClass, classCounter); // add our own class + + boolean memberTooLowLevel = false; for (String toolTipLine : itemTooltip) { Matcher playerDetailMatcher = DUNGEON_PARTY_FINDER_PLAYER.matcher(EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine)); if (playerDetailMatcher.matches()) { - String clazz = playerDetailMatcher.group(1); - int classLevel = MathHelper.parseIntWithDefault(playerDetailMatcher.group(2), -1); - if (MooConfig.dungFilterPartiesWithDupes && !dungClassesInParty.add(clazz)) { - // duped class! - status = "²âş"; // 2+ - color = new Color(220, 120, 20, 255); - break; - } else if (dungClassMin > -1 && classLevel < dungClassMin) { - // party member too low level - status = EnumChatFormatting.BOLD + "áŻ"; - color = new Color(200, 20, 20, 255); - break; - } else if (dungClassMax > -1 && classLevel > dungClassMax) { - // party member too high level - status = EnumChatFormatting.BOLD + "áą"; - color = new Color(20, 120, 230, 255); - break; + String className = playerDetailMatcher.group(1); + DungeonClass clazz = DungeonClass.get(className); + dungClassesInParty.putIfAbsent(clazz, new AtomicInteger(0)); + dungClassesInParty.get(clazz).incrementAndGet(); + + int classLevel = MathHelper.parseIntWithDefault(playerDetailMatcher.group(2), 100); + if (classLevel < MooConfig.dungClassMin) { + memberTooLowLevel = true; } } } + if (memberTooLowLevel) { + GlStateManager.pushMatrix(); + GlStateManager.translate(0, 0, 280); + Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(EnumChatFormatting.BOLD + "áŻ", x + 1, y + 8, new Color(220, 20, 20, 255).getRGB()); + GlStateManager.popMatrix(); + } + StringBuilder dupedClasses = new StringBuilder(); + for (Map.Entry<DungeonClass, AtomicInteger> partyClassInfo : dungClassesInParty.entrySet()) { + if (partyClassInfo.getValue().get() > 1 && MooConfig.filterDungPartiesWithDupes(partyClassInfo.getKey())) { + dupedClasses.append(partyClassInfo.getKey().getShortName()); + } + } + if (dupedClasses.length() > 0) { + dupedClasses.insert(0, EnumChatFormatting.RED).insert(0, "²âş"); + + GlStateManager.pushMatrix(); + GlStateManager.translate(0, 0, 280); + double scaleFactor = 0.8; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(dupedClasses.toString(), (float) (x / scaleFactor), (float) (y / scaleFactor), new Color(255, 170, 0, 255).getRGB()); + GlStateManager.popMatrix(); + } else if (!memberTooLowLevel) { + // party matches our criteria! + indicatorItem = new ItemStack(Blocks.carpet, 1, EnumDyeColor.LIME.getMetadata()); + } + } + if (indicatorItem != null) { + GlStateManager.enableRescaleNormal(); + RenderHelper.enableGUIStandardItemLighting(); + Minecraft.getMinecraft().getRenderItem().renderItemIntoGUI(indicatorItem, x, y); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableRescaleNormal(); } - Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(status, x, y, color.getRGB()); } // Events inside dungeons @@ -543,8 +569,8 @@ public class DungeonsListener { dungeonCache.updateElapsedMinutesFromScoreboard(); } - boolean isConfigGui = Minecraft.getMinecraft().currentScreen instanceof DungeonOverlayGuiConfig; - if (MooConfig.dungOverlayEnabled && dungeonCache.isInDungeon() || isConfigGui) { + boolean isEditingDungeonOverlaySettings = MooConfigGui.showDungeonPerformanceOverlay(); + if (MooConfig.dungOverlayEnabled && dungeonCache.isInDungeon() || isEditingDungeonOverlaySettings) { ArrayList<String> dungeonPerformanceEntries = new ArrayList<>(); int maxSkillScore = dungeonCache.getMaxSkillScore(); int totalDeaths = dungeonCache.getTotalDeaths(); @@ -554,10 +580,10 @@ public class DungeonsListener { int elapsedMinutes = dungeonCache.getElapsedMinutes(); dungeonPerformanceEntries.add("Max Skill score: " + (maxSkillScore == 100 ? EnumChatFormatting.GREEN : EnumChatFormatting.YELLOW) + maxSkillScore + " / 100"); - if (totalDeaths > 0 || isConfigGui) { + if (totalDeaths > 0 || isEditingDungeonOverlaySettings) { dungeonPerformanceEntries.add(" Deaths: " + EnumChatFormatting.RED + totalDeaths); } - if (failedPuzzles > 0 || isConfigGui) { + if (failedPuzzles > 0 || isEditingDungeonOverlaySettings) { dungeonPerformanceEntries.add(" Failed Puzzles: " + EnumChatFormatting.RED + failedPuzzles); } dungeonPerformanceEntries.add("Class Milestone: " + (classMilestone < 3 ? EnumChatFormatting.RED : EnumChatFormatting.GREEN) + classMilestone); @@ -579,12 +605,18 @@ public class DungeonsListener { FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; GlStateManager.pushMatrix(); + float scaleFactor = MooConfig.dungOverlayGuiScale / 100f; GlStateManager.scale(scaleFactor, scaleFactor, 0); for (int line = 0; line < dungeonPerformanceEntries.size(); line++) { String dungeonPerformanceEntry = dungeonPerformanceEntries.get(line); - int yPos = MooConfig.dungOverlayPositionY + fontRenderer.FONT_HEIGHT * line + 2; - fontRenderer.drawString(dungeonPerformanceEntry, MooConfig.dungOverlayPositionX, yPos, 0xffFFAA00); + int xPos = (int) ((e.resolution.getScaledWidth() * (MooConfig.dungOverlayPositionX / 1000d)) / scaleFactor); + int yPos = (int) ((e.resolution.getScaledHeight() * (MooConfig.dungOverlayPositionY / 1000d) + 2) / scaleFactor + fontRenderer.FONT_HEIGHT * line + 2); + if (MooConfig.dungOverlayTextShadow) { + fontRenderer.drawStringWithShadow(dungeonPerformanceEntry, xPos, yPos, 0xffFFAA00); + } else { + fontRenderer.drawString(dungeonPerformanceEntry, xPos, yPos, 0xffFFAA00); + } } GlStateManager.popMatrix(); } diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java index d6b94fc..29b5e51 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java @@ -18,7 +18,6 @@ import net.minecraftforge.common.util.Constants; import net.minecraftforge.event.entity.player.ItemTooltipEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.apache.commons.lang3.StringUtils; -import org.lwjgl.input.Keyboard; import java.text.NumberFormat; import java.text.ParseException; @@ -73,12 +72,13 @@ public class SkyBlockListener { } } - if (!MooConfig.showAdvancedTooltips && !Keyboard.isKeyDown(Keyboard.KEY_LMENU)) { - return; - } + MooConfig.Setting tooltipItemAgeDisplay = MooConfig.getTooltipItemAgeDisplay(); + MooConfig.Setting tooltipItemTimestampDisplay = MooConfig.getTooltipItemTimestampDisplay(); + // add item age to tooltip NBTTagCompound extraAttributes = e.itemStack.getSubCompound("ExtraAttributes", false); - if (extraAttributes != null && extraAttributes.hasKey("timestamp")) { + if (extraAttributes != null && extraAttributes.hasKey("timestamp") + && (tooltipItemAgeDisplay != MooConfig.Setting.DISABLED || tooltipItemTimestampDisplay != MooConfig.Setting.DISABLED)) { String rawTimestamp = extraAttributes.getString("timestamp"); Matcher sbTimestampMatcher = SB_TIMESTAMP_PATTERN.matcher(rawTimestamp); if (sbTimestampMatcher.matches()) { @@ -88,19 +88,29 @@ public class SkyBlockListener { int index = Math.max(0, e.toolTip.size() - (e.showAdvancedItemTooltips ? /* item name & nbt info */ 2 : 0)); - if (Keyboard.isKeyDown(Keyboard.KEY_LMENU)) { - // full tooltip - e.toolTip.add(index, "Timestamp: " + EnumChatFormatting.DARK_GRAY + dateTimeFormatted); - e.toolTip.add(index, "Item age: " + EnumChatFormatting.DARK_GRAY + Utils.getDurationAsWords(dateTime.toEpochSecond() * 1000).first()); - } else { - // abbreviated tooltip - e.toolTip.add(index, "Item age: " + EnumChatFormatting.DARK_GRAY + Utils.getDurationAsWord(dateTime.toEpochSecond() * 1000)); + switch (tooltipItemTimestampDisplay) { + case SPECIAL: + if (!MooConfig.isTooltipToggleKeyBindingPressed()) { + break; + } + case ALWAYS: + e.toolTip.add(index, "Timestamp: " + EnumChatFormatting.DARK_GRAY + dateTimeFormatted); + } + switch (tooltipItemAgeDisplay) { + case SPECIAL: + if (!MooConfig.isTooltipToggleKeyBindingPressed()) { + break; + } + case ALWAYS: + e.toolTip.add(index, "Item age: " + EnumChatFormatting.DARK_GRAY + ((MooConfig.tooltipItemAgeShortened) ? Utils.getDurationAsWord(dateTime.toEpochSecond() * 1000) : Utils.getDurationAsWords(dateTime.toEpochSecond() * 1000).first())); } } } // for auction house: show price for each item if multiple items are sold at once - if (e.entityPlayer != null && e.entityPlayer.openContainer instanceof ContainerChest) { + MooConfig.Setting tooltipAuctionHousePriceEachDisplay = MooConfig.getTooltipAuctionHousePriceEachDisplay(); + if ((tooltipAuctionHousePriceEachDisplay == MooConfig.Setting.ALWAYS || tooltipAuctionHousePriceEachDisplay == MooConfig.Setting.SPECIAL && MooConfig.isTooltipToggleKeyBindingPressed()) + && e.entityPlayer != null && e.entityPlayer.openContainer instanceof ContainerChest) { int stackSize = e.itemStack.stackSize; if ((stackSize == 1 && !isSubmitBidItem(e.itemStack)) || e.toolTip.size() < 4) { // only 1 item or irrelevant tooltip - nothing to do here, abort! @@ -110,11 +120,11 @@ public class SkyBlockListener { if (isSubmitBidItem(e.itemStack)) { // special case: "place bid on an item" interface ("Auction View") ItemStack auctionedItem = e.entityPlayer.openContainer.getInventory().get(13); - stackSize = auctionedItem.stackSize; - if (stackSize == 1) { + if (auctionedItem == null || auctionedItem.stackSize == 1) { // still only 1 item, abort! return; } + stackSize = auctionedItem.stackSize; } List<String> toolTip = e.toolTip; @@ -124,6 +134,7 @@ public class SkyBlockListener { String toolTipLineUnformatted = EnumChatFormatting.getTextWithoutFormattingCodes(toolTip.get(i)); if (toolTipLineUnformatted.startsWith("Top bid: ") || toolTipLineUnformatted.startsWith("Starting bid: ") + || toolTipLineUnformatted.startsWith("Create BIN Auction: ") || toolTipLineUnformatted.startsWith("Buy it now: ") || toolTipLineUnformatted.startsWith("Sold for: ") || toolTipLineUnformatted.startsWith("New bid: ") /* special case: 'Submit Bid' item */) { diff --git a/src/main/java/de/cowtipper/cowlection/search/GuiSearch.java b/src/main/java/de/cowtipper/cowlection/search/GuiSearch.java index ed1a1d9..f9b68a1 100644 --- a/src/main/java/de/cowtipper/cowlection/search/GuiSearch.java +++ b/src/main/java/de/cowtipper/cowlection/search/GuiSearch.java @@ -5,6 +5,7 @@ import com.mojang.realmsclient.util.Pair; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.data.LogEntry; +import de.cowtipper.cowlection.util.GuiHelper; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.*; @@ -13,11 +14,13 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; -import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Property; import net.minecraftforge.fml.client.GuiScrollingList; import net.minecraftforge.fml.client.config.GuiButtonExt; import net.minecraftforge.fml.client.config.GuiCheckBox; -import net.minecraftforge.fml.client.config.GuiUtils; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.IConfigElement; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.reflect.FieldUtils; @@ -67,6 +70,7 @@ public class GuiSearch extends GuiScreen { private GuiButton buttonSearch; private GuiButton buttonClose; private GuiButton buttonHelp; + private GuiButton buttonSettings; private GuiCheckBox checkboxChatOnly; private GuiCheckBox checkboxMatchCase; private GuiCheckBox checkboxRemoveFormatting; @@ -107,6 +111,9 @@ public class GuiSearch extends GuiScreen { public void initGui() { this.guiTooltips = new ArrayList<>(); + // recalculate start date + this.dateStart = MooConfig.calculateStartDate(); + this.fieldSearchQuery = new GuiTextField(42, this.fontRendererObj, this.width / 2 - 100, 13, 200, 20); this.fieldSearchQuery.setMaxStringLength(255); this.fieldSearchQuery.setText(searchQuery); @@ -131,8 +138,11 @@ public class GuiSearch extends GuiScreen { this.buttonList.add(this.buttonClose = new GuiButtonExt(0, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); addTooltip(buttonClose, Arrays.asList(EnumChatFormatting.RED + "Close search interface", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC")); // help - this.buttonList.add(this.buttonHelp = new GuiButtonExt(1, this.width - 25 - 25, 3, 22, 20, "?")); + this.buttonList.add(this.buttonHelp = new GuiButtonExt(1, this.width - 2 * 25, 3, 22, 20, "?")); addTooltip(buttonHelp, Collections.singletonList(EnumChatFormatting.YELLOW + "Show help")); + // settings + this.buttonList.add(this.buttonSettings = new GuiButtonExt(1, this.width - 3 * 25, 3, 22, 20, "")); + addTooltip(buttonSettings, Collections.singletonList(EnumChatFormatting.YELLOW + "Open Settings")); // chatOnly this.buttonList.add(this.checkboxChatOnly = new GuiCheckBox(21, this.width / 2 - 100, 35, " Chatbox only", chatOnly)); @@ -255,10 +265,11 @@ public class GuiSearch extends GuiScreen { this.guiSearchResults.drawScreen(mouseX, mouseY, partialTicks); super.drawScreen(mouseX, mouseY, partialTicks); + GuiHelper.drawSprite(this.width - 3 * 25 + 2, 3, 36, 18, 500); for (GuiTooltip guiTooltip : guiTooltips) { if (guiTooltip.checkHover(mouseX, mouseY)) { - drawHoveringText(guiTooltip.getText(), mouseX, mouseY, 300); + GuiHelper.drawHoveringText(guiTooltip.getText(), mouseX, mouseY, width, height, 300); // only one tooltip can be displayed at a time: break! break; } @@ -310,10 +321,10 @@ public class GuiSearch extends GuiScreen { } else if (button == buttonHelp) { this.areEntriesSearchResults = false; this.searchResults.clear(); - this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Initial setup/Configuration " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "/moo config")); + this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Initial setup/configuration " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "'Open Settings' (top right corner)")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 1) " + EnumChatFormatting.RESET + "Configure directories that should be scanned for log files (\"Directories with Minecraft log files\")")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 2) " + EnumChatFormatting.RESET + "Set default starting date (\"Start date for log file search\")")); - this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Performing a search " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "/moo search")); + this.searchResults.add(new LogEntry("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Performing a search " + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "/moo search [initial search term]")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 1) " + EnumChatFormatting.RESET + "Enter search term")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 2) " + EnumChatFormatting.RESET + "Adjust start and end date")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " 3) " + EnumChatFormatting.RESET + "Select desired options (match case, ...)")); @@ -324,6 +335,15 @@ public class GuiSearch extends GuiScreen { this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "CTRL + A " + EnumChatFormatting.RESET + "to copy all search results")); this.searchResults.add(new LogEntry(EnumChatFormatting.GOLD + " - " + EnumChatFormatting.YELLOW + "Double click search result " + EnumChatFormatting.RESET + "to open corresponding log file in default text editor")); this.guiSearchResults.setResults(searchResults); + } else if (button == buttonSettings) { + List<IConfigElement> logSearchConfigElements = new ArrayList<>(); + for (Property configEntry : Cowlection.getInstance().getConfig().getLogSearchProperties()) { + logSearchConfigElements.add(new ConfigElement(configEntry)); + } + mc.displayGuiScreen(new GuiConfig(this, + logSearchConfigElements, + Cowlection.MODID, "cowlectionLogSearchConfig", false, false, + EnumChatFormatting.GOLD + "Press Done to save changes.")); } } @@ -350,17 +370,6 @@ public class GuiSearch extends GuiScreen { } } - private void drawHoveringText(List<String> textLines, int mouseX, int mouseY, int maxTextWidth) { - if (ForgeVersion.getBuildVersion() < 1808) { - // we're running a forge version from before 24 March 2016 (http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.8.9.html for reference) - // using mc built-in method - drawHoveringText(textLines, mouseX, mouseY, fontRendererObj); - } else { - // we're on a newer forge version, so we can use the improved tooltip rendering added in 1.8.9-11.15.1.1808 (released 03/24/16 09:25 PM) in this pull request: https://github.com/MinecraftForge/MinecraftForge/pull/2649 - GuiUtils.drawHoveringText(textLines, mouseX, mouseY, width, height, maxTextWidth, fontRendererObj); - } - } - /** * List gui element similar to GuiModList.Info */ @@ -454,7 +463,7 @@ public class GuiSearch extends GuiScreen { int left = width / 2 - stringWidth / 2 - margin; int top = height / 2 - margin; drawRect(left, top, left + stringWidth + 2 * margin, top + fontRendererObj.FONT_HEIGHT + 2 * margin, 0xff000000); - drawCenteredString(fontRendererObj, errorText,/* 2, 30*/width / 2, height / 2, 0xffDD1111); + drawCenteredString(fontRendererObj, errorText, width / 2, height / 2, 0xffDD1111); } else { errorMessage = null; } diff --git a/src/main/java/de/cowtipper/cowlection/search/LogFilesSearcher.java b/src/main/java/de/cowtipper/cowlection/search/LogFilesSearcher.java index 636a46d..2e811ef 100644 --- a/src/main/java/de/cowtipper/cowlection/search/LogFilesSearcher.java +++ b/src/main/java/de/cowtipper/cowlection/search/LogFilesSearcher.java @@ -48,7 +48,7 @@ class LogFilesSearcher { } if (files.isEmpty()) { - throw new FileNotFoundException(EnumChatFormatting.DARK_RED + "ERROR: Couldn't find any Minecraft log files. Please check if the log file directories are set correctly (/moo config)."); + throw new FileNotFoundException(EnumChatFormatting.DARK_RED + "ERROR: Couldn't find any Minecraft log files. Please check if the log file directories are set correctly (Log Search \u27A1 Settings)."); } else { List<LogEntry> searchResults = analyzeFiles(files, searchQuery, chatOnly, matchCase, removeFormatting) .stream().sorted(Comparator.comparing(LogEntry::getTime)).collect(Collectors.toList()); @@ -65,7 +65,7 @@ class LogFilesSearcher { : new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(path.toFile())))))) { // ....log.gz String fileName = path.getFileName().toString(); // 2020-04-20-3.log.gz String date = fileName.equals("latest.log") - ? LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) + ? DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault()).format(Files.getLastModifiedTime(path).toInstant()) : fileName.substring(0, fileName.lastIndexOf('-')); String content; LogEntry logEntry = null; diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java index 8b37cf7..442c940 100644 --- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java @@ -31,6 +31,7 @@ public class ApiUtils { private static final String ONLINE_STATUS_URL = "https://api.hypixel.net/status?key=%s&uuid=%s"; private static final String SKYBLOCK_STATS_URL = "https://api.hypixel.net/skyblock/profiles?key=%s&uuid=%s"; private static final String PLAYER_URL = "https://api.hypixel.net/player?key=%s&uuid=%s"; + private static final String API_KEY_URL = "https://api.hypixel.net/key?key=%s"; private static final ExecutorService pool = Executors.newCachedThreadPool(); private ApiUtils() { @@ -120,6 +121,21 @@ public class ApiUtils { return null; } + public static void fetchApiKeyInfo(String moo, ThrowingConsumer<HyApiKey> action) { + pool.execute(() -> action.accept(getApiKeyInfo(moo))); + } + + private static HyApiKey getApiKeyInfo(String moo) { + try (BufferedReader reader = makeApiCall(String.format(API_KEY_URL, moo))) { + if (reader != null) { + return GsonUtils.fromJson(reader, HyApiKey.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return null; + } + private static BufferedReader makeApiCall(String url) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setConnectTimeout(5000); diff --git a/src/main/java/de/cowtipper/cowlection/util/Utils.java b/src/main/java/de/cowtipper/cowlection/util/Utils.java index e833cf8..24e6b7b 100644 --- a/src/main/java/de/cowtipper/cowlection/util/Utils.java +++ b/src/main/java/de/cowtipper/cowlection/util/Utils.java @@ -111,7 +111,7 @@ public final class Utils { private static String formatNumberWithAbbreviations(double number, int iteration) { @SuppressWarnings("IntegerDivisionInFloatingPointContext") double d = ((long) number / 100) / 10.0; - boolean isRound = (d * 10) % 10 == 0; //true if the decimal part is equal to 0 (then it's trimmed anyway) + boolean isRound = (d * 10) % 10 == 0; // true if the decimal part is equal to 0 (then it's trimmed anyway) // this determines the class, i.e. 'k', 'm' etc // this decides whether to trim the decimals // (int) d * 10 / 10 drops the decimal diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang index 0bcad5d..14bb5f7 100644 --- a/src/main/resources/assets/cowlection/lang/en_US.lang +++ b/src/main/resources/assets/cowlection/lang/en_US.lang @@ -1,31 +1,67 @@ +cowlection.config.isMooValid=API key valid? §d§l⡠+cowlection.config.isMooValid.tooltip=You can use /moo apikey to see how to request a new API key from Hypixel\n§eConfig entries marked with §d§l⡠§erequire a valid API key. +cowlection.config.configGuiExplanations=Show config categories explanations... +cowlection.config.configGuiExplanations.tooltip=How should the explanations (introductory words, hints, and tips) of the individual configuration categories be displayed? +cowlection.config.tabCompletableNamesCommands=Commands with Tab-completable usernames +cowlection.config.tabCompletableNamesCommands.tooltip=List of commands with a username argument that should be Tab-completable.\n§eRequires game restart to take effect! +cowlection.config.gotoLogSearchConfig=Search through your Minecraft logs +cowlection.config.gotoLogSearchConfig.tooltip=Settings can be accessed at the top right corner +cowlection.config.logsDirs=Directories with Minecraft log files +cowlection.config.logsDirs.tooltip=List of directories containing Minecraft log files +cowlection.config.defaultStartDate=Start date for log file search §c[see tooltip!] +cowlection.config.defaultStartDate.tooltip=§eCan be either a §6number§e (e.g. "§63§e" means "§6start searching 3 months ago§e"),\n§eor alternatively a §6fixed date §e(§6yyyy-mm-dd§e) +cowlection.config.gotoKeyBindings=Change key bindings +cowlection.config.gotoKeyBindings.tooltip=Some key bindings can be found in the 'Cowlection' sub-category cowlection.config.doUpdateCheck=Check for updates -cowlection.config.doUpdateCheck.tooltip=Check for mod updates? +cowlection.config.doUpdateCheck.tooltip=Check for mod updates when launching the game? cowlection.config.showBestFriendNotifications=Show best friend notifications cowlection.config.showBestFriendNotifications.tooltip=Set to true to receive best friends' login/logout messages, set to false hide them. cowlection.config.showFriendNotifications=Show friend notifications cowlection.config.showFriendNotifications.tooltip=Set to true to receive friends' login/logout messages, set to false hide them. cowlection.config.showGuildNotifications=Show guild notifications cowlection.config.showGuildNotifications.tooltip=Set to true to receive guild members' login/logout messages, set to false hide them. -cowlection.config.doBestFriendsOnlineCheck=Do best friends online check -cowlection.config.doBestFriendsOnlineCheck.tooltip=Set to true to check best friends' online status when joining a server, set to false to disable.\n§fDoes §dnot §fwork for staff members and players hiding their online status. -cowlection.config.showAdvancedTooltips=Show advanced tooltips -cowlection.config.showAdvancedTooltips.tooltip=Set to true to show advanced tooltips, set to false show default tooltips. +cowlection.config.doBestFriendsOnlineCheck=Do best friends online check §d§l⡠+cowlection.config.doBestFriendsOnlineCheck.tooltip=Set to true to check best friends' online status when joining a server, set to false to disable.\n§fDoes §dnot §fwork for staff members and players hiding their online status.\n§d§l⡠§eRequires a valid API key! +cowlection.config.tooltipToggleKeyBinding=Key binding: toggle tooltip +cowlection.config.tooltipToggleKeyBinding.tooltip=Hold down this key to toggle tooltip if one of the following settings is set to 'key press' +cowlection.config.tooltipAuctionHousePriceEach=Auction house: price per item +cowlection.config.tooltipAuctionHousePriceEach.tooltip=Add price per item if multiple items are bought or sold? +cowlection.config.tooltipItemAge=Show item age +cowlection.config.tooltipItemAge.tooltip=Show item age? Only works for non-stackable items +cowlection.config.tooltipItemAgeShortened=Shorten item age? +cowlection.config.tooltipItemAgeShortened.tooltip=Show shortened (5.8 months) or detailed item age (5 months 24 days)? +cowlection.config.tooltipItemTimestamp=Show item creation date +cowlection.config.tooltipItemTimestamp.tooltip=Show item creation date? Only works for non-stackable items cowlection.config.numeralSystem=Numeral system -cowlection.config.numeralSystem.tooltip=Use Roman or Arabic numeral system? -cowlection.config.tabCompletableNamesCommands=Commands with Tab-completable usernames -cowlection.config.tabCompletableNamesCommands.tooltip=List of commands with a username argument that should be Tab-completable.\nRequires game restart to take effect! -cowlection.config.dungClassRange=Dungeon Parties: Class level range -cowlection.config.dungClassRange.tooltip=Accepted level range for the dungeon party finder. Set to -1 to disable -cowlection.config.dungFilterPartiesWithDupes=Dungeon Parties: Mark duplicated classes? -cowlection.config.dungFilterPartiesWithDupes.tooltip=Mark parties with duplicated classes? -cowlection.config.dungPartyFinderArmorLookup=Dungeon Parties: Show armor... -cowlection.config.dungPartyFinderArmorLookup.tooltip=Show armor of player joining via party finder as a tooltip or in chat? -cowlection.config.dungItemQualityPos=Dungeon tooltips: Item Quality positon +cowlection.config.numeralSystem.tooltip=Use Roman or Arabic numeral system?\nThis is currently used to display numbers in the commands /moo stalkSkyBlock and /moo analyzeIsland +cowlection.config.dungItemToolTipToggleKeyBinding=Key binding: toggle dungeon item tooltip +cowlection.config.dungItemToolTipToggleKeyBinding.tooltip=Hold down this key to toggle dungeon item tooltip +cowlection.config.dungItemQualityPos=Item Quality position (hold above keybind) cowlection.config.dungItemQualityPos.tooltip=Position of item quality in dungeon item tooltips -cowlection.config.logsDirs=Directories with Minecraft log files -cowlection.config.logsDirs.tooltip=List of directories containing Minecraft log files -cowlection.config.defaultStartDate=Start date for log file search -cowlection.config.defaultStartDate.tooltip=§eCan be either a §6number§e (e.g. "§63§e" means "§6start searching 3 months ago§e"),\n§eor alternatively a §6fixed date §e(§6yyyy-mm-dd§e) +cowlection.config.dungOverlayEnabled=Show overlay inside dungeons? +cowlection.config.dungOverlayEnabled.tooltip=Show the performance overlay while inside a dungeon? +cowlection.config.dungOverlayPositionX=Overlay x position (âŚ/⨠keys to fine-tune) +cowlection.config.dungOverlayPositionX.tooltip=Use left and right arrow key to increment/decrement the x position by 1 +cowlection.config.dungOverlayPositionY=Overlay y position (âŚ/⨠keys to fine-tune) +cowlection.config.dungOverlayPositionY.tooltip=Use left and right arrow key to increment/decrement the y position by 1 +cowlection.config.dungOverlayGuiScale=Overlay Gui Scale +cowlection.config.dungOverlayGuiScale.tooltip=Adjust the size of the dungeon performance overlay (50%%-200%%) +cowlection.config.dungOverlayTextShadow=Add text shadow +cowlection.config.dungOverlayTextShadow.tooltip=Enable or disable text shadow +cowlection.config.dungClassMin=Minimum preferred class level +cowlection.config.dungClassMin.tooltip=Marks parties with members with lower class level than this value +cowlection.config.dungFilterPartiesWithArcherDupes=Mark duplicated Archer class? +cowlection.config.dungFilterPartiesWithArcherDupes.tooltip=Mark parties with duplicated Archer class? +cowlection.config.dungFilterPartiesWithBerserkDupes=Mark duplicated Berserk class? +cowlection.config.dungFilterPartiesWithBerserkDupes.tooltip=Mark parties with duplicated Berserk class? +cowlection.config.dungFilterPartiesWithHealerDupes=Mark duplicated Healer class? +cowlection.config.dungFilterPartiesWithHealerDupes.tooltip=Mark parties with duplicated Healer class? +cowlection.config.dungFilterPartiesWithMageDupes=Mark duplicated Mage class? +cowlection.config.dungFilterPartiesWithMageDupes.tooltip=Mark parties with duplicated Mage class? +cowlection.config.dungFilterPartiesWithTankDupes=Mark duplicated Tank class? +cowlection.config.dungFilterPartiesWithTankDupes.tooltip=Mark parties with duplicated Tank class? +cowlection.config.dungPartyFinderArmorLookup=Show armor of joining player... §d§l⡠+cowlection.config.dungPartyFinderArmorLookup.tooltip=Show armor of player joining via party finder as a tooltip or in chat?\n§d§l⡠§eRequires a valid API key! cowlection.commands.generic.exception=%s key.cowlection.category=Cowlection -key.cowlection.moo.desc=Open Command +key.cowlection.moo=Open Command |