From 0ce8dc4ff8fa01fa17963c2ca7577700956ecf43 Mon Sep 17 00:00:00 2001 From: Cow Date: Sat, 22 Jul 2023 16:01:03 +0200 Subject: Removed best friends list and join/leave notifications toggles Both features have been implemented by Hypixel --- CHANGELOG.md | 12 + README.md | 2 - .../cowtipper/cowlection/command/MooCommand.java | 182 ++++----- .../cowlection/config/CredentialStorage.java | 2 +- .../de/cowtipper/cowlection/config/MooConfig.java | 76 ++-- .../config/gui/MooConfigBestFriendsMigration.java | 409 +++++++++++++++++++++ .../cowlection/error/ApiAskPolitelyErrorEvent.java | 15 - .../cowlection/handler/FriendsHandler.java | 195 +--------- .../cowtipper/cowlection/handler/PlayerCache.java | 16 +- .../cowlection/listener/ChatListener.java | 145 +++----- .../cowlection/listener/PlayerListener.java | 31 +- .../cowlection/partyfinder/RuleEditorGui.java | 2 +- .../de/cowtipper/cowlection/util/ApiUtils.java | 27 -- .../cowtipper/cowlection/util/VersionChecker.java | 2 +- .../resources/assets/cowlection/lang/en_US.lang | 12 +- 15 files changed, 610 insertions(+), 518 deletions(-) create mode 100644 src/main/java/de/cowtipper/cowlection/config/gui/MooConfigBestFriendsMigration.java delete mode 100644 src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2efdc95..3c8a717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 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.16.0] - unreleased +### Removed +- Removed Cowlection 'Best friends' list: + - Migrate your current Cowlection best friends with `/moo bestfriends` + - Hypixel added their own 'Best friends' list quite a while ago: [see patch notes for 'Social Update'](https://hypixel.net/threads/social-update-online-status-best-friends-more.4638020/) + - Hypixel's best friends list does basically the same, plus doesn't require *any* API requests + - View best friends list: `/friend list best` or `/fl best` + - Add or remove best friend: `/friend best ` +- Removed Join & leave notifications toggle for friends, best friends, and guild members + - use Hypixel's commands instead: + - `/friend notifications` to cycle through the available Friends notifications options, or `/settings` → Social Settings → Friend Notifications: All / Best / None + - `/guild notifications` to toggle Guild notifications, or `/settings` → Personal Guild Settings → Guild Notifications + ### Changed - Dungeons overlay: now disabled by default (old config entries aren't modified) - SkyBlock player lookup: removed 'last played/last profile save' as it's no longer part of the API diff --git a/README.md b/README.md index 4ead20b..71c460b 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,8 @@ It is a collection of different features mainly focused on Hypixel SkyBlock. | 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 | `/moo search` (click the `?` for more info) | | Stalk a player (check online status, current game, ...) | `/moo stalk` | -| Toggle join/leave notifications for friends, guild members or best friends separately | `/moo config` → Notifications | | Show all client-side commands added by all installed mods | `/commandslist` | | Copy chat component | ALT + right click
Hold shift to copy full component | | Copy inventories to clipboard as JSON | CTRL + C (single item)
CTRL + SHIFT + C (whole inventory) | diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java index 9c950b6..c1baff9 100644 --- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java +++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java @@ -10,6 +10,7 @@ import de.cowtipper.cowlection.command.exception.InvalidPlayerNameException; import de.cowtipper.cowlection.command.exception.MooCommandException; import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.config.gui.MooConfigBestFriendsMigration; import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.data.*; import de.cowtipper.cowlection.data.HySkyBlockStats.Profile.Pet; @@ -19,6 +20,7 @@ import de.cowtipper.cowlection.partyfinder.RuleEditorGui; import de.cowtipper.cowlection.search.GuiSearch; import de.cowtipper.cowlection.util.*; import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.entity.EntityOtherPlayerMP; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.multiplayer.WorldClient; @@ -93,16 +95,23 @@ public class MooCommand extends CommandBase { || args[0].equalsIgnoreCase("s") || args[0].equalsIgnoreCase("askPolitelyWhereTheyAre")) { handleStalking(args); - } else if (args[0].equalsIgnoreCase("add")) { - handleBestFriendAdd(args); + } else if (args[0].equalsIgnoreCase("add") + || args[0].equalsIgnoreCase("list") + || args[0].equalsIgnoreCase("online") + || args[0].equalsIgnoreCase("nameChangeCheck")) { + handleBestFriendSubcommands(); } else if (args[0].equalsIgnoreCase("remove")) { handleBestFriendRemove(args); - } else if (args[0].equalsIgnoreCase("list")) { - handleListBestFriends(); - } else if (args[0].equalsIgnoreCase("online")) { - handleBestFriendsOnlineCheck(); - } else if (args[0].equalsIgnoreCase("nameChangeCheck")) { - handleNameChangeCheck(args); + } else if (args[0].equalsIgnoreCase("bestfriends") || args[0].equalsIgnoreCase("bestfriend")) { + displayGuiScreen(new MooConfigBestFriendsMigration()); + } else if (args[0].equalsIgnoreCase("I-read-the-login-logout-notification-changes")) { + main.getConfig().acknowledgeLoginLogoutNotificationChanges(); + MooChatComponent confirmationMsg = new MooChatComponent("[§2Cowlection§a] The 'login & logout notification filter' removal info message will no longer be shown.").green(); + if (main.getFriendsHandler().getBestFriendsListSize() > 0) { + confirmationMsg.appendSibling(new MooChatComponent("\nHowever, don't forget to migrate your Cowlection best friends by using §4/moo bestfriends§c!").red()) + .setSuggestCommand("/moo bestfriends"); + } + main.getChatHelper().sendMessage(confirmationMsg); } // + toggle (= alias for config) //endregion @@ -193,7 +202,7 @@ public class MooCommand extends CommandBase { //region sub commands: Best friends, friends & other players private void handleStalking(String[] args) throws CommandException { 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 " + EnumChatFormatting.RED + " to manually set your existing API key."); + throw new MooCommandException("You haven't set your Hypixel API key yet or the API key is invalid. Use " + EnumChatFormatting.DARK_RED + "/" + this.getCommandName() + " apikey " + EnumChatFormatting.RED + " to manually set your existing API key."); } if (args.length != 2) { throw new WrongUsageException("/" + getCommandName() + " stalk "); @@ -202,24 +211,18 @@ public class MooCommand extends CommandBase { } else { String playerName = args[1]; main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Stalking " + EnumChatFormatting.WHITE + playerName + EnumChatFormatting.GRAY + ". This may take a few seconds."); - boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, true); - if (isBestFriend) { - Friend stalkedPlayer = main.getFriendsHandler().getBestFriend(playerName); - // we have the uuid already, so stalk the player - stalkPlayer(stalkedPlayer); - } else { - // fetch player uuid - ApiUtils.fetchFriendData(playerName, stalkedPlayer -> { - if (stalkedPlayer == null) { - throw new ApiContactException("Mojang", "couldn't stalk " + EnumChatFormatting.DARK_RED + playerName); - } else if (stalkedPlayer.equals(Friend.FRIEND_NOT_FOUND)) { - throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + "."); - } else { - // ... then stalk the player - stalkPlayer(stalkedPlayer); - } - }); - } + + // fetch player uuid + ApiUtils.fetchFriendData(playerName, stalkedPlayer -> { + if (stalkedPlayer == null) { + throw new ApiContactException("Mojang", "couldn't stalk " + EnumChatFormatting.DARK_RED + playerName); + } else if (stalkedPlayer.equals(Friend.FRIEND_NOT_FOUND)) { + throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + "."); + } else { + // ... then stalk the player + stalkPlayer(stalkedPlayer); + } + }); } } @@ -267,20 +270,11 @@ public class MooCommand extends CommandBase { }); } - private void handleBestFriendAdd(String[] args) throws CommandException { - if (args.length != 2) { - throw new WrongUsageException("/" + getCommandName() + " add "); - } else if (Utils.isInvalidMcName(args[1])) { - throw new InvalidPlayerNameException(args[1]); - } else if (main.getFriendsHandler().isBestFriend(args[1], true)) { - throw new MooCommandException(EnumChatFormatting.DARK_RED + args[1] + EnumChatFormatting.RED + " is a best friend already."); - } else if (main.getFriendsHandler().getBestFriends().size() >= 100) { - throw new MooCommandException(EnumChatFormatting.RED + "The best friends list is limited to 100 players. Remove some with " + EnumChatFormatting.WHITE + "/" + getCommandName() + " remove " + EnumChatFormatting.RED + "first"); - } else { - main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "Fetching " + EnumChatFormatting.YELLOW + args[1] + EnumChatFormatting.GOLD + "'s unique user id. This may take a few seconds..."); - // add friend async - main.getFriendsHandler().addBestFriend(args[1]); - } + private void handleBestFriendSubcommands() { + main.getChatHelper().sendMessage(new MooChatComponent("[" + EnumChatFormatting.DARK_RED + Cowlection.MODNAME + EnumChatFormatting.RED + "] The 'best friends list' feature has been removed from this mod.").red() + .appendSibling(new MooChatComponent(" Run " + EnumChatFormatting.GOLD + "/moo bestfriends " + EnumChatFormatting.YELLOW + "to migrate your best friends list").yellow()) + .setSuggestCommand("/moo bestfriends")); + Minecraft.getMinecraft().thePlayer.playSound("mob.villager.no", Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER), 1.4f); } private void handleBestFriendRemove(String[] args) throws CommandException { @@ -297,51 +291,12 @@ public class MooCommand extends CommandBase { throw new MooCommandException(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " isn't a best friend."); } } - - private void handleListBestFriends() { - Set bestFriends = main.getFriendsHandler().getBestFriends(); - - // TODO show fancy gui with list of best friends; maybe with buttons to delete them - main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "➜ Best friends" - + (bestFriends.isEmpty() ? "" : " (" + EnumChatFormatting.DARK_GREEN + bestFriends.size() + EnumChatFormatting.GREEN + ")") + ": " - + ((bestFriends.isEmpty()) - ? EnumChatFormatting.ITALIC + "none :c" - : 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 " + 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 "); - } - } - - private void handleNameChangeCheck(String[] args) throws CommandException { - if (args.length != 2) { - throw new WrongUsageException("/" + getCommandName() + " nameChangeCheck "); - } else if (Utils.isInvalidMcName(args[1])) { - throw new InvalidPlayerNameException(args[1]); - } - Friend bestFriend = main.getFriendsHandler().getBestFriend(args[1]); - if (bestFriend.equals(Friend.FRIEND_NOT_FOUND)) { - throw new MooCommandException(EnumChatFormatting.DARK_RED + args[1] + EnumChatFormatting.RED + " isn't a best friend."); - } else { - main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "Checking if " + bestFriend.getName() + " changed their name... This will take a few seconds..."); - // check for name change async - main.getFriendsHandler().doBestFriendNameChangeCheck(bestFriend, true); - } - } //endregion //region sub commands: SkyBlock private void handleStalkingSkyBlock(String[] args) throws CommandException { 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 " + EnumChatFormatting.RED + " to manually set your existing API key."); + throw new MooCommandException("You haven't set your Hypixel API key yet or the API key is invalid. Use " + EnumChatFormatting.DARK_RED + "/" + this.getCommandName() + " apikey " + EnumChatFormatting.RED + " to manually set your existing API key."); } if (args.length != 2) { throw new WrongUsageException("/" + getCommandName() + " skyblockstalk "); @@ -350,24 +305,17 @@ public class MooCommand extends CommandBase { } else { String playerName = args[1]; main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Stalking " + EnumChatFormatting.WHITE + playerName + EnumChatFormatting.GRAY + "'s SkyBlock stats. This may take a few seconds."); - boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, true); - if (isBestFriend) { - Friend stalkedPlayer = main.getFriendsHandler().getBestFriend(playerName); - // we have the uuid already, so stalk the player - stalkSkyBlockStats(stalkedPlayer); - } else { - // fetch player uuid - ApiUtils.fetchFriendData(playerName, stalkedPlayer -> { - if (stalkedPlayer == null) { - throw new ApiContactException("Mojang", "couldn't stalk " + EnumChatFormatting.DARK_RED + playerName); - } else if (stalkedPlayer.equals(Friend.FRIEND_NOT_FOUND)) { - throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + "."); - } else { - // ... then stalk the player - stalkSkyBlockStats(stalkedPlayer); - } - }); - } + // fetch player uuid + ApiUtils.fetchFriendData(playerName, stalkedPlayer -> { + if (stalkedPlayer == null) { + throw new ApiContactException("Mojang", "couldn't stalk " + EnumChatFormatting.DARK_RED + playerName); + } else if (stalkedPlayer.equals(Friend.FRIEND_NOT_FOUND)) { + throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + "."); + } else { + // ... then stalk the player + stalkSkyBlockStats(stalkedPlayer); + } + }); } } @@ -879,7 +827,7 @@ public class MooCommand extends CommandBase { } else if ((args.length == 2 && (args[1].equalsIgnoreCase("party") || args[1].equalsIgnoreCase("p"))) || args.length == 1 && args[0].equalsIgnoreCase("dp")) { 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 " + EnumChatFormatting.RED + " to manually set your existing API key."); + throw new MooCommandException("You haven't set your Hypixel API key yet or the API key is invalid. Use " + EnumChatFormatting.DARK_RED + "/" + this.getCommandName() + " apikey " + EnumChatFormatting.RED + " to manually set your existing API key."); } else if (dungeonsPartyListener != null) { throw new MooCommandException("Please wait a few seconds before using this command again."); } @@ -951,14 +899,14 @@ public class MooCommand extends CommandBase { color = EnumChatFormatting.RED; colorSecondary = EnumChatFormatting.DARK_RED; } - main.getChatHelper().sendMessage(color, firstSentence + color + " Use " + colorSecondary + "/api new" + color + " to request a new API key from Hypixel or use " + colorSecondary + "/" + this.getCommandName() + " apikey " + color + " to manually set your existing API key."); + main.getChatHelper().sendMessage(color, firstSentence + color + " Use " + colorSecondary + "/" + this.getCommandName() + " apikey " + color + " to manually set your existing API key."); } else { String key = args[1]; if (Utils.isValidUuid(key)) { main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "[" + Cowlection.MODNAME + "] Validating API key..."); main.getMoo().setMooIfValid(key, true); } else { - throw new SyntaxErrorException("[" + Cowlection.MODNAME + "] That doesn't look like a valid API key... Did you want check your API key usage statistics? Run /" + getCommandName() + " apikey"); + throw new SyntaxErrorException("[" + Cowlection.MODNAME + "] That doesn't look like a valid API key..."); } } } @@ -1062,18 +1010,11 @@ public class MooCommand extends CommandBase { ? new MooChatComponent("\n").reset().white().appendText(EnumChatFormatting.DARK_GREEN + " ❢" + EnumChatFormatting.LIGHT_PURPLE + EnumChatFormatting.ITALIC + " To move the Dungeons overlay: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " config " + EnumChatFormatting.GRAY + "➡ " + EnumChatFormatting.WHITE + "SB Dungeons " + EnumChatFormatting.GRAY + "➡ " + EnumChatFormatting.WHITE + "Dungeon Performance Overlay") : null; - IChatComponent usage = new MooChatComponent("➜ " + Cowlection.MODNAME + " commands:").gold().bold() + MooChatComponent usage = new MooChatComponent("➜ " + Cowlection.MODNAME + " commands:").gold().bold() .appendSibling(createCmdHelpEntry("config", "Open mod's configuration")) .appendSibling(dungeonOverlayHint) .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 §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 §d§l⚷")) - .appendSibling(createCmdHelpEntry("nameChangeCheck", "Force a scan for a changed name of a best friend (is done automatically as well)")) - .appendSibling(createCmdHelpSection(2, "SkyBlock")) + .appendSibling(createCmdHelpSection(1, "SkyBlock")) .appendSibling(createCmdHelpEntry("stalkskyblock", "Get info of player's SkyBlock stats §d§l⚷")) .appendSibling(createCmdHelpEntry("analyzeChests", "Analyze chests' contents and evaluate potential Bazaar value")) .appendSibling(createCmdHelpEntry("analyzeIsland", "Analyze a SkyBlock private island (inspect minions)")) @@ -1081,19 +1022,23 @@ public class MooCommand extends CommandBase { .appendSibling(createCmdHelpEntry("dungeon", "SkyBlock Dungeons: display current dungeon performance")) .appendSibling(createCmdHelpEntry("dungeon party", "SkyBlock Dungeons: Shows armor and dungeon info about current party members " + EnumChatFormatting.GRAY + "(alias: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " dp" + EnumChatFormatting.GRAY + ") §d§l⚷")) .appendSibling(createCmdHelpEntry("dungeon rules", "SkyBlock Dungeons: Edit rules for Party Finder " + EnumChatFormatting.GRAY + "(alias: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " dr" + EnumChatFormatting.GRAY + ")")) - .appendSibling(createCmdHelpSection(3, "Miscellaneous")) + .appendSibling(createCmdHelpSection(2, "Miscellaneous")) .appendSibling(createCmdHelpEntry("search", "Open Minecraft log search")) .appendSibling(createCmdHelpEntry("worldage", "Check how long the current world is loaded")) + .appendSibling(createCmdHelpEntry("stalk", "Get info of player's online status §d§l⚷")) .appendSibling(createCmdHelpEntry("guiScale", "Change GUI scale")) .appendSibling(createCmdHelpEntry("rr", "Alias for /r without auto-replacement to /msg")) .appendSibling(createCmdHelpEntry("shrug", "¯\\_(ツ)_/¯")) .appendSibling(createCmdHelpEntry("discord", "Need help? Join the Cowshed discord")) - .appendSibling(createCmdHelpSection(4, "Update mod")) + .appendSibling(createCmdHelpSection(3, "Update mod")) .appendSibling(createCmdHelpEntry("update", "Check for new mod updates")) .appendSibling(createCmdHelpEntry("updateHelp", "Show mod update instructions")) .appendSibling(createCmdHelpEntry("version", "View results of last mod update check")) - .appendSibling(createCmdHelpEntry("directory", "Open Minecraft's mods directory")) - .appendFreshSibling(new MooChatComponent("➡ /commandslist " + EnumChatFormatting.YELLOW + "to list all commands added by your installed mods.").lightPurple().setSuggestCommand("/commandslist")) + .appendSibling(createCmdHelpEntry("directory", "Open Minecraft's mods directory")); + if (main.getFriendsHandler().getBestFriendsListSize() > 0) { + usage.appendSibling(createCmdHelpEntry("bestfriends", "§dMigrate best friends list")); + } + usage.appendFreshSibling(new MooChatComponent("➡ /commandslist " + EnumChatFormatting.YELLOW + "to list all commands added by your installed mods.").lightPurple().setSuggestCommand("/commandslist")) .appendFreshSibling(new MooChatComponent("➜ Need help with " + EnumChatFormatting.GOLD + Cowlection.MODNAME + EnumChatFormatting.GREEN + "? Do you have any questions, suggestions or other feedback? " + EnumChatFormatting.GOLD + "Join the Cowshed discord!").green().setUrl(Cowlection.INVITE_URL)); sender.addChatMessage(usage); } @@ -1119,11 +1064,12 @@ public class MooCommand extends CommandBase { if (args.length == 1) { return getListOfStringsMatchingLastWord(args, /* main */ "help", "config", - /* Best friends, friends & other players */ "stalk", "add", "remove", "list", "online", "nameChangeCheck", + /* Best friends, friends & other players */ "stalk", /* SkyBlock */ "stalkskyblock", "skyblockstalk", "chestAnalyzer", "analyzeChests", "analyzeIsland", "waila", "whatAmILookingAt", "dungeon", /* miscellaneous */ "search", "worldage", "serverage", "guiscale", "rr", "shrug", "apikey", "discord", /* update mod */ "update", "updateHelp", "version", "directory", - /* rarely used aliases */ "askPolitelyWhereTheyAre", "askPolitelyAboutTheirSkyBlockProgress", "year", "whatyearisit"); + /* rarely used aliases */ "askPolitelyWhereTheyAre", "askPolitelyAboutTheirSkyBlockProgress", "year", "whatyearisit", + /* deprecated as of 0.16.0: */ "bestfriends", "remove"); } else if (args.length == 2 && (args[0].equalsIgnoreCase("waila") || args[0].equalsIgnoreCase("whatAmILookingAt"))) { return getListOfStringsMatchingLastWord(args, "all", "main"); } else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { @@ -1136,7 +1082,7 @@ public class MooCommand extends CommandBase { return getListOfStringsMatchingLastWord(args, "stop"); } String commandArg = args[0].toLowerCase(); - if (args.length == 2 && (commandArg.equals("s") || commandArg.equals("ss") || commandArg.equals("namechangecheck") || commandArg.contains("stalk") || commandArg.contains("askpolitely"))) { // stalk & stalkskyblock + namechangecheck + if (args.length == 2 && (commandArg.equals("s") || commandArg.equals("ss") || commandArg.contains("stalk") || commandArg.contains("askpolitely"))) { // stalk & stalkskyblock return getListOfStringsMatchingLastWord(args, main.getPlayerCache().getAllNamesSorted()); } return null; diff --git a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java index 86deda9..5b1b0b1 100644 --- a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java +++ b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java @@ -71,7 +71,7 @@ public class CredentialStorage { // uhm... looks like someone added the certs to the default JKS already return; } - System.out.println("Injecting Let's Encrypt support due to ancient Java version..."); + Cowlection.getInstance().getLogger().info("Injecting Let's Encrypt support due to ancient Java version..."); try { KeyStore originalKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); originalKeyStore.load(Files.newInputStream(Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts")), "changeit".toCharArray()); diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java index 92632f3..9a9ba4b 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -11,16 +11,13 @@ import de.cowtipper.cowlection.partyfinder.Rule; import de.cowtipper.cowlection.util.MooChatComponent; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; -import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.gui.GuiScreen; import net.minecraft.command.ICommand; import net.minecraft.item.ItemStack; 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; @@ -66,8 +63,6 @@ public class MooConfig { private static int maxLatestLogFileSize; // Category: Notifications public static boolean doUpdateCheck; - public static boolean showBestFriendNotifications; - public static boolean enableBestFriendNotificationSound; public static boolean showFriendNotifications; public static boolean showGuildNotifications; public static boolean doBestFriendsOnlineCheck; @@ -325,6 +320,10 @@ public class MooConfig { "configGuiExplanations", "as tooltip", "Display config settings explanations", new String[]{"as tooltip", "as text", "hidden"})); + // Sub-Category: Mod update checker + subCat = configCat.addSubCategory("Mod update checker"); + Property propDoUpdateCheck = subCat.addConfigEntry(cfg.get("notifications", // legacy category + "doUpdateCheck", true, "Check for mod updates?")); // (not visible in config gui: has opened config? or: knows how to move the dungeon overlay?) Property propHasOpenedConfigGui = cfg.get(configCat.getConfigName(), @@ -360,7 +359,7 @@ public class MooConfig { " ‣ 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)"); + " ‣ Friends join & leave messages"); propTabCompletableNamesCommands = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "tabCompletableNamesCommands", new String[]{"p", "ah", "ignore", "msg", "tell", "w", "boop", "profile", "friend", "friends"}, "List of commands with a Tab-completable username argument.") @@ -386,49 +385,22 @@ public class MooConfig { logSearchProperties.add(propMaxLogFileSize); logSearchProperties.add(propMaxLatestLogFileSize); - // 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 " + 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 propEnableBestFriendNotificationSound = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), - "enableBestFriendNotificationSound", false, "Set to true to play a notification sound when a best friend comes online"), - new MooConfigPreview("random.pop", Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER), 1)); - - 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."))); + // Category "Notifications" > Sub-Category: Login & Logout + // deprecated as of 0.16.0 + String legacyConfigCategory = "notifications"; + Property propShowFriendNotifications = cfg.get(legacyConfigCategory, + "showFriendNotifications", true, "[DEPRECATED as of 0.16.0] Set to true to receive friends' login/logout messages, set to false hide them.") + .setShowInGui(false); + Property propShowGuildNotifications = cfg.get(legacyConfigCategory, + "showGuildNotifications", true, "[DEPRECATED as of 0.16.0] Set to true to receive guild members' login/logout messages, set to false hide them.") + .setShowInGui(false); // 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")))); + // deprecated as of 0.16.0 + Property propDoBestFriendsOnlineCheck = cfg.get(legacyConfigCategory, + "doBestFriendsOnlineCheck", false, "[DEPRECATED as of 0.16.0] Set to true to check best friends' online status when joining a server, set to false to disable.") + .setShowInGui(false); // Category: SkyBlock @@ -760,8 +732,6 @@ public class MooConfig { maxLatestLogFileSize = propMaxLatestLogFileSize.getInt(); // Category: Notifications doUpdateCheck = propDoUpdateCheck.getBoolean(); - showBestFriendNotifications = propShowBestFriendNotifications.getBoolean(); - enableBestFriendNotificationSound = propEnableBestFriendNotificationSound.getBoolean(); showFriendNotifications = propShowFriendNotifications.getBoolean(); showGuildNotifications = propShowGuildNotifications.getBoolean(); doBestFriendsOnlineCheck = propDoBestFriendsOnlineCheck.getBoolean(); @@ -854,8 +824,6 @@ public class MooConfig { propMaxLatestLogFileSize.set(maxLatestLogFileSize); // Category: Notifications propDoUpdateCheck.set(doUpdateCheck); - propShowBestFriendNotifications.set(showBestFriendNotifications); - propEnableBestFriendNotificationSound.set(enableBestFriendNotificationSound); propShowFriendNotifications.set(showFriendNotifications); propShowGuildNotifications.set(showGuildNotifications); propDoBestFriendsOnlineCheck.set(doBestFriendsOnlineCheck); @@ -1052,7 +1020,13 @@ public class MooConfig { * @return true if notifications should be monitored */ public static boolean doMonitorNotifications() { - return showBestFriendNotifications || enableBestFriendNotificationSound || !showFriendNotifications || !showGuildNotifications; + return !showFriendNotifications || !showGuildNotifications; + } + + public void acknowledgeLoginLogoutNotificationChanges() { + showFriendNotifications = true; + showGuildNotifications = true; + syncFromFields(); } // Category: SkyBlock diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigBestFriendsMigration.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigBestFriendsMigration.java new file mode 100644 index 0000000..3ff1730 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigBestFriendsMigration.java @@ -0,0 +1,409 @@ +package de.cowtipper.cowlection.config.gui; + +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.util.MooChatComponent; +import de.cowtipper.cowlection.util.TickDelay; +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.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.client.GuiScrollingList; +import net.minecraftforge.fml.client.config.GuiButtonExt; +import net.minecraftforge.fml.client.config.GuiConfigEntries; +import net.minecraftforge.fml.client.config.GuiEditArrayEntries; +import net.minecraftforge.fml.client.config.GuiUtils; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.awt.*; +import java.net.URI; +import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Based on GuiModList + */ +public class MooConfigBestFriendsMigration extends GuiScreen { + /** + *
+     * §9§m-----------------------------------------------------
+     * §r§a[VIP] PLAYER§r§a is now a best friend!§r§9§m
+     * -----------------------------------------------------§r
+     *
+     * §9§m-----------------------------------------------------
+     * §r§a[VIP] PLAYER§r§e is no longer a best friend!§r§9§m
+     * -----------------------------------------------------§r
+     *
+     * §9§m-----------------------------------------------------
+     * §r§6[MVP§r§d++§r§6] PLAYER§r§c isn't on your friends list!§r§9§m
+     * -----------------------------------------------------§r
+     * 
+ */ + private static final Pattern BEST_FRIEND_CHAT_PATTERN = Pattern.compile("-{20,}\\s*(?:\\[[^]]+] )?(\\w+)([^\n]+)\\s*-{20,}"); + + private BestFriendsListGui bestFriendsGui; + private GuiButton btnRemoveAll; + private GuiButton btnHelp; + private GuiButton btnClose; + private boolean isMigrating = false; + + @Override + public void onGuiClosed() { + MinecraftForge.EVENT_BUS.unregister(this); + if (Cowlection.getInstance().getFriendsHandler().getBestFriendsListSize() == 0) { + new TickDelay(() -> Cowlection.getInstance().getChatHelper().sendMessage(new MooChatComponent("[§2Cowlection§a] All Cowlection best friends have been migrated or removed.").green() + .appendFreshSibling(new MooChatComponent("You can now use Hypixel's commands instead: §b[open patch notes]").green().setUrl("https://hypixel.net/threads/social-update-online-status-best-friends-more.4638020/") + .appendFreshSibling(new MooChatComponent(" §6➊ §aView best friends list: §2/friend list best §aor §2/fl best").green().setSuggestCommand("/fl best")) + .appendFreshSibling(new MooChatComponent(" §6➋ §aAdd or remove best friend: §2/friend best ").green().setSuggestCommand("/f best ")))), + isMigrating ? 20 : 0); + } + } + + // priority = highest to ignore other mods modifying the chat output + @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) + public void onBestFriendsAddResponse(ClientChatReceivedEvent e) { + if (e.type == 2 || !isMigrating) return; + String text = EnumChatFormatting.getTextWithoutFormattingCodes(e.message.getUnformattedText()); + if (!text.startsWith("-----------------------------") || !text.endsWith("-----------------------------")) + return; + Matcher matcher = BEST_FRIEND_CHAT_PATTERN.matcher(text); + if (!matcher.matches()) return; + String username = matcher.group(1); + String rawFriendStatus = matcher.group(2); + + int friendStatus; + if (rawFriendStatus.contains("no longer a best friend")) { + friendStatus = 0xFFaa0000; + } else if (rawFriendStatus.contains("now a best friend")) { + friendStatus = 0xFF00aa00; + } else if (rawFriendStatus.contains("isn't on your friends list")) { + friendStatus = 0xFFff5555; + } else { + return; + } + + this.bestFriendsGui.setBestFriendStatus(username, friendStatus); + } + + @Override + public void initGui() { + if (Cowlection.getInstance().getFriendsHandler().getBestFriendsListSize() == 0) { + mc.displayGuiScreen(null); + return; + } + MinecraftForge.EVENT_BUS.register(this); + + this.buttonList.clear(); + + // remove all button + this.buttonList.add(this.btnRemoveAll = new GuiButtonExt(3, this.width - 69, 4, 20, 20, EnumChatFormatting.DARK_RED + "♺")); + + // help button + this.buttonList.add(this.btnHelp = new GuiButtonExt(1, this.width - 47, 4, 20, 20, "?")); + // close button + this.buttonList.add(this.btnClose = new GuiButtonExt(2, this.width - 25, 4, 20, 20, EnumChatFormatting.RED + "X")); + + // scrollable gui + this.bestFriendsGui = new BestFriendsListGui(this.width / 2, this.height, this.isMigrating); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.drawGradientRect(this.width / 2, 0, this.width, this.height, -1072689136, -804253680); // from #drawDefaultBackground + super.drawScreen(mouseX, mouseY, partialTicks); + GlStateManager.pushMatrix(); + double scaleFactor = 1.3; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + this.drawString(this.fontRendererObj, "Cowlection: Best friends migration", (int) ((this.width / 2 + 8) / scaleFactor), 6, 0xFFCC00); + GlStateManager.popMatrix(); + this.bestFriendsGui.drawScreen(mouseX, mouseY, partialTicks); + if (btnRemoveAll.isMouseOver()) { + List removeAllTooltip = new ArrayList<>(); + removeAllTooltip.add(EnumChatFormatting.RED + "Discard/Remove " + EnumChatFormatting.DARK_RED + "all " + EnumChatFormatting.RED + "Cowlection best friends"); + drawHoveringText(removeAllTooltip, mouseX, mouseY); + } else if (btnHelp.isMouseOver()) { + List helpTooltip = new ArrayList<>(); + helpTooltip.add("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Cowlection: Best friends migration"); + helpTooltip.add(EnumChatFormatting.GREEN + "If you have any questions or need help with the best friends list migration:"); + helpTooltip.add(EnumChatFormatting.GREEN + "Join the Cowshed discord and open a ticket!"); + drawHoveringText(helpTooltip, mouseX, mouseY); + } else if (btnClose.isMouseOver()) { + drawHoveringText(Arrays.asList(EnumChatFormatting.RED + "Save & Close", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC"), mouseX, mouseY); + } + } + + @Override + protected void actionPerformed(GuiButton button) { + if (button == btnRemoveAll) { + // show best friends deletion confirmation screen + GuiYesNo guiDeleteRule = new GuiYesNo(MooConfigBestFriendsMigration.this, + EnumChatFormatting.RED + "Discard/remove " + EnumChatFormatting.DARK_RED + Cowlection.getInstance().getFriendsHandler().getBestFriendsListSize() + " (= all)" + EnumChatFormatting.RED + " from Cowlection best friends list?", + EnumChatFormatting.RED + "This action cannot be reverted!", + 1337); + mc.displayGuiScreen(guiDeleteRule); + } else if (button == btnHelp) { + GuiConfirmOpenLink guiHelp = new GuiConfirmOpenLink(this, Cowlection.INVITE_URL, 9001, true); + guiHelp.disableSecurityWarning(); + mc.displayGuiScreen(guiHelp); + } else if (button == btnClose) { + mc.displayGuiScreen(null); + } + } + + @Override + public void confirmClicked(boolean result, int id) { + super.confirmClicked(result, id); + if (result) { + if (id == 1337) { + Cowlection.getInstance().getFriendsHandler().removeAllBestFriends(); + } else if (id == 9001) { + try { + Desktop.getDesktop().browse(new URI(Cowlection.INVITE_URL)); + } catch (Throwable throwable) { + Cowlection.getInstance().getLogger().error("Couldn't open link " + Cowlection.INVITE_URL, throwable); + } + } else if (id < 500) { + // user confirmed rule deletion + BestFriendsListGui.BestFriendEntry removedBestFriendEntry = this.bestFriendsGui.bestFriendEntries.get(id); + if (removedBestFriendEntry != null) { + removedBestFriendEntry.removeBestFriend(); + } + } + } + mc.displayGuiScreen(this); + } + + /** + * Based on GuiModList.Info + */ + private class BestFriendsListGui extends GuiScrollingList { + private final MooChatComponent BEST_FRIENDS_MIGRATIONS_GUIDE = new MooChatComponent("The 'best friends list' feature has been removed from this mod, as Hypixel added a very similar feature ").red() + .appendSibling(new MooChatComponent("[click to open patch notes]") + .darkAqua() + .setUrl("https://hypixel.net/threads/social-update-online-status-best-friends-more.4638020/")) + .appendFreshSibling(new MooChatComponent("You can either…\n §e➊ §ftry to add someone from your Cowlection best friends list to your Hypixel best friends list (§2+§f button), or\n §e➋ §fdiscard/remove them from your Cowlection best friends list without trying to add them on Hypixel as a best friend (§c♺§f button)." + + "\n§4§l❢ §eOnly players who are already on your §6normal §eHypixel friends list can be added as a best friends.")) + .appendFreshSibling(new MooChatComponent("➜ Start migrating my clicking the button at the top.").gray()); + + private final List introduction; + private final List bestFriendEntries; + private final boolean isMigrating; + private final GuiButton startMigratingButton; + private BestFriendEntry hoveredEntry; + + public BestFriendsListGui(int width, int height, boolean isMigrating) { + super(MooConfigBestFriendsMigration.this.mc, + width - 6, height, + 27, height - 3, width + 3, 18, + MooConfigBestFriendsMigration.this.width, MooConfigBestFriendsMigration.this.height); + setHeaderInfo(true, isMigrating ? 20 : 30); + + this.bestFriendEntries = isMigrating ? convertToEntries() : Collections.emptyList(); + this.introduction = isMigrating ? Collections.emptyList() : this.resizeContent(); + this.startMigratingButton = isMigrating ? null : new GuiButtonExt(21000, this.left + 25, 0, 170, 20, EnumChatFormatting.DARK_GREEN + "➜ " + EnumChatFormatting.GREEN + "Start best friends migration"); + this.isMigrating = isMigrating; + } + + private List convertToEntries() { + Set bestFriends = Cowlection.getInstance().getFriendsHandler().getBestFriends(); + + return bestFriends.stream().map(BestFriendEntry::new).collect(Collectors.toList()); + } + + public void setBestFriendStatus(String username, int friendStatus) { + for (BestFriendEntry bestFriendEntry : this.bestFriendEntries) { + if (bestFriendEntry.bestFriend.equals(username)) { + bestFriendEntry.setStatus(friendStatus); + } + } + } + + @Override + protected void drawHeader(int entryRight, int relativeY, Tessellator tess) { + if (relativeY < 0) return; + boolean hasEntriesLeft = this.bestFriendEntries.size() > 0; + if (hasEntriesLeft) { + MooConfigBestFriendsMigration.this.fontRendererObj.drawStringWithShadow(EnumChatFormatting.GREEN + "Migrate " + EnumChatFormatting.WHITE + "or " + EnumChatFormatting.RED + "remove " + EnumChatFormatting.WHITE + "best friend", this.left + 6, relativeY + 5, 0xFFCC00); + } else if (!isMigrating && startMigratingButton != null) { + this.startMigratingButton.yPosition = relativeY + 3; + this.startMigratingButton.drawButton(mc, mouseX, mouseY); + } + } + + @Override + protected void clickHeader(int x, int y) { + super.clickHeader(x, y); + if (this.startMigratingButton.isMouseOver()) { + MooConfigBestFriendsMigration.this.isMigrating = true; + MooConfigBestFriendsMigration.this.initGui(); + } + } + + private List resizeContent() { + return new ArrayList<>(GuiUtilRenderComponents.splitText(BEST_FRIENDS_MIGRATIONS_GUIDE, + this.listWidth - 8, MooConfigBestFriendsMigration.this.fontRendererObj, false, true)); + } + + + @Override + protected int getSize() { + return isMigrating ? this.bestFriendEntries.size() : this.introduction.size(); + } + + @Override + protected void elementClicked(int index, boolean doubleClick) { + if (isMigrating) { + this.bestFriendEntries.get(index).mousePressed(mouseX, mouseY, index); + } else { + IChatComponent line = introduction.get(index); + if (line != null) { + int xOffset = this.left; + for (IChatComponent part : line) { + if (!(part instanceof ChatComponentText)) { + continue; + } + xOffset += MooConfigBestFriendsMigration.this.fontRendererObj.getStringWidth(((ChatComponentText) part).getChatComponentText_TextValue()); + if (xOffset >= this.mouseX) { + MooConfigBestFriendsMigration.this.handleComponentClick(part); + break; + } + } + } + } + } + + @Override + protected boolean isSelected(int index) { + return false; + } + + @Override + protected void drawBackground() { + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + hoveredEntry = null; + super.drawScreen(mouseX, mouseY, partialTicks); + if (hoveredEntry != null) { + hoveredEntry.drawToolTip(mouseX, mouseY); + } + } + + @Override + protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { + if (this.isMigrating) { + BestFriendEntry bestFriendEntry = bestFriendEntries.get(slotIdx); + if (bestFriendEntry != null) { + bestFriendEntry.drawEntry(this.left + 4, slotTop, this.right - 4); + if (mouseY >= slotTop && mouseY < slotTop + this.slotHeight) { + // mouse is over this slot + hoveredEntry = bestFriendEntry; + } + } + } else { + IChatComponent line = introduction.get(slotIdx); + if (line != null) { + MooConfigBestFriendsMigration.this.fontRendererObj.drawStringWithShadow(line.getFormattedText(), this.left + 4, slotTop, 0xFFffffff); + } + } + } + + /** + * Based on: + * + * @see GuiConfigEntries.StringEntry + * @see GuiEditArrayEntries.BaseEntry + */ + private class BestFriendEntry { + private final String bestFriend; + private final GuiButton btnAdd; + private final List btnAddTooltip; + private final GuiButton btnRemove; + private final List btnRemoveTooltip; + private int friendStatus = 0xFFffffff; + + public BestFriendEntry(String bestFriend) { + this.bestFriend = bestFriend; + + this.btnAdd = new GuiButtonExt(50, 0, 0, 18, 16, "+"); + this.btnAdd.packedFGColour = GuiUtils.getColorCode('2', true); + this.btnAddTooltip = new ArrayList<>(); + this.btnAddTooltip.add("Try to §aadd §e" + bestFriend + " §fto Hypixel best friends list"); + this.btnAddTooltip.add("§7Runs the command §8/friend best " + bestFriend); + + this.btnRemove = new GuiButtonExt(50, 0, 0, 18, 16, "♺"); + this.btnRemove.packedFGColour = GuiUtils.getColorCode('c', true); + this.btnRemoveTooltip = new ArrayList<>(); + this.btnRemoveTooltip.add("§cRemove §e" + bestFriend + " §ffrom your Cowlection best friends list"); + this.btnRemoveTooltip.add("§7Runs the command §8/moo remove " + bestFriend); + } + + public void setStatus(int friendStatus) { + this.friendStatus = friendStatus; + } + + public void drawEntry(int x, int y, int right) { + int currentX = x; + + btnAdd.xPosition = currentX; + btnAdd.yPosition = y - 1; + btnAdd.drawButton(mc, mouseX, mouseY); + currentX += btnAdd.width + 3; + + btnRemove.xPosition = currentX; + btnRemove.yPosition = y - 1; + btnRemove.drawButton(mc, mouseX, mouseY); + currentX += btnRemove.width + 3; + + MooConfigBestFriendsMigration.this.fontRendererObj.drawStringWithShadow(this.bestFriend, (currentX + 1), (y + 4), friendStatus); + + if (this.friendStatus < 0xFFffffff) { + drawRect(x, y - 1, right, y + 15, 0x99666666); + } + } + + public void drawToolTip(int mouseX, int mouseY) { + if (btnAdd.isMouseOver()) { + drawHoveringText(this.btnAddTooltip, mouseX, mouseY); + } else if (btnRemove.isMouseOver()) { + drawHoveringText(btnRemoveTooltip, mouseX, mouseY); + } + } + + public void mousePressed(int mouseX, int mouseY, int slotIndex) { + if (btnAdd.mousePressed(mc, mouseX, mouseY)) { + Minecraft.getMinecraft().thePlayer.sendChatMessage("/friend best " + this.bestFriend); + this.removeBestFriend(); + } else if (btnRemove.mousePressed(mc, mouseX, mouseY)) { + if (isShiftKeyDown()) { + this.removeBestFriend(); + } else { + // show best friend deletion confirmation screen + GuiYesNo guiDeleteRule = new GuiYesNo(MooConfigBestFriendsMigration.this, + EnumChatFormatting.RED + "Discard/remove " + EnumChatFormatting.YELLOW + bestFriend + EnumChatFormatting.RESET + " from Cowlection best friends list?", + EnumChatFormatting.GRAY + "Hint: You can hold " + EnumChatFormatting.WHITE + "SHIFT " + EnumChatFormatting.GRAY + "while clicking the " + EnumChatFormatting.RED + "♺ " + EnumChatFormatting.GRAY + "button to skip this confirmation screen.", + slotIndex); + mc.displayGuiScreen(guiDeleteRule); + } + } + } + + public void removeBestFriend() { + Cowlection.getInstance().getFriendsHandler().removeBestFriend(this.bestFriend); + this.friendStatus = 0xFFbbbbbb; + if (Cowlection.getInstance().getFriendsHandler().getBestFriendsListSize() == 0) { + mc.displayGuiScreen(null); + } + } + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java b/src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java deleted file mode 100644 index 5da2dfb..0000000 --- a/src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.cowtipper.cowlection.error; - -import net.minecraftforge.fml.common.eventhandler.Event; - -public class ApiAskPolitelyErrorEvent extends Event { - private final String playerName; - - public ApiAskPolitelyErrorEvent(String playerName) { - this.playerName = playerName; - } - - public String getPlayerName() { - return playerName; - } -} diff --git a/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java b/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java index 9034b17..3691d12 100644 --- a/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java +++ b/src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java @@ -2,213 +2,57 @@ package de.cowtipper.cowlection.handler; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; -import com.mojang.realmsclient.util.Pair; import de.cowtipper.cowlection.Cowlection; -import de.cowtipper.cowlection.command.exception.ApiContactException; -import de.cowtipper.cowlection.command.exception.MooCommandException; +import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.data.Friend; -import de.cowtipper.cowlection.data.HyPlayerData; -import de.cowtipper.cowlection.util.*; +import de.cowtipper.cowlection.util.GsonUtils; import io.netty.util.internal.ConcurrentSet; -import net.minecraft.client.Minecraft; -import net.minecraft.command.PlayerNotFoundException; -import net.minecraft.event.ClickEvent; -import net.minecraft.event.HoverEvent; -import net.minecraft.util.ChatComponentText; -import net.minecraft.util.ChatStyle; -import net.minecraft.util.EnumChatFormatting; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.time.DurationFormatUtils; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; public class FriendsHandler { - private static final long UPDATE_FREQUENCY_DEFAULT = TimeUnit.HOURS.toMillis(10); private final Cowlection main; private final Set bestFriends = new ConcurrentSet<>(); private final File bestFriendsFile; - private final AtomicInteger bestFriendNameCheckingQueue = new AtomicInteger(); - private final AtomicInteger bestFriendOnlineStatusQueue = new AtomicInteger(); - private final Set bestFriendsOnlineStatusWithApiErrors = new ConcurrentSet<>(); - private long nextBestFriendOnlineCheck = 0; public FriendsHandler(Cowlection main, File friendsFile) { this.main = main; this.bestFriendsFile = friendsFile; loadBestFriends(); - doBestFriendsNameChangeCheck(); - } - - public boolean isBestFriend(String playerName, boolean ignoreCase) { - if (ignoreCase) { - return bestFriends.stream().map(Friend::getName).anyMatch(playerName::equalsIgnoreCase); - } else { - return bestFriends.stream().map(Friend::getName).anyMatch(playerName::equals); - } - } - - public void addBestFriend(String name) { - if (name.isEmpty()) { - return; - } - - ApiUtils.fetchFriendData(name, friend -> { - if (friend == null) { - throw new ApiContactException("Mojang", "didn't add " + name + " as a best friend."); - } else if (friend.equals(Friend.FRIEND_NOT_FOUND)) { - throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + name + EnumChatFormatting.RED + "."); - } else { - boolean added = bestFriends.add(friend); - if (added) { - main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Added " + EnumChatFormatting.DARK_GREEN + friend.getName() + EnumChatFormatting.GREEN + " as best friend."); - saveBestFriends(); - } - } - }); } public boolean removeBestFriend(String name) { boolean removed = bestFriends.removeIf(friend -> friend.getName().equalsIgnoreCase(name)); if (removed) { + if (bestFriends.size() == 0) { + MooConfig.doBestFriendsOnlineCheck = false; + main.getConfig().syncFromFields(); + } saveBestFriends(); } return removed; } - public Set getBestFriends() { - return bestFriends.stream().map(Friend::getName).collect(Collectors.toCollection(TreeSet::new)); - } - - public Friend getBestFriend(String name) { - return bestFriends.stream().filter(friend -> friend.getName().equalsIgnoreCase(name)).findFirst().orElse(Friend.FRIEND_NOT_FOUND); - } - - private Friend getBestFriend(UUID uuid) { - return bestFriends.stream().filter(friend -> friend.getUuid().equals(uuid)).findFirst().orElse(Friend.FRIEND_NOT_FOUND); - } - - public void doBestFriendsNameChangeCheck() { - bestFriends.stream().filter(friend -> System.currentTimeMillis() - friend.getLastChecked() > UPDATE_FREQUENCY_DEFAULT) - .forEach(friend1 -> { - bestFriendNameCheckingQueue.incrementAndGet(); - doBestFriendNameChangeCheck(friend1, false); - }); + public void removeAllBestFriends() { + bestFriends.clear(); + MooConfig.doBestFriendsOnlineCheck = false; + main.getConfig().syncFromFields(); + saveBestFriends(); } - public void doBestFriendNameChangeCheck(Friend friend, boolean isCommandTriggered) { - ApiUtils.fetchCurrentName(friend, newName -> { - if (newName == null) { - // skipping friend, something went wrong with API request - if (isCommandTriggered) { - throw new ApiContactException("Mojang", "couldn't check " + EnumChatFormatting.DARK_RED + friend.getName() + EnumChatFormatting.RED + " (possible) new player name"); - } - } else if (newName.equals(ApiUtils.UUID_NOT_FOUND)) { - throw new PlayerNotFoundException("How did you manage to get a unique id on your best friends list that has no name attached to it?"); - } else if (newName.equals(friend.getName())) { - // name hasn't changed, only updating lastChecked timestamp - Friend bestFriend = getBestFriend(friend.getUuid()); - if (!bestFriend.equals(Friend.FRIEND_NOT_FOUND)) { - bestFriend.setLastChecked(System.currentTimeMillis()); - if (isCommandTriggered) { - throw new MooCommandException(friend.getName() + " hasn't changed their name"); - } - } - } else { - // name has changed - main.getChatHelper().sendMessage(new ChatComponentText("Your best friend " + EnumChatFormatting.DARK_GREEN + friend.getName() + EnumChatFormatting.GREEN + " changed the name to " + EnumChatFormatting.DARK_GREEN + newName + EnumChatFormatting.GREEN + ".").setChatStyle(new ChatStyle() - .setColor(EnumChatFormatting.GREEN) - .setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://namemc.com/search?q=" + newName)) - .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "View " + EnumChatFormatting.GOLD + newName + EnumChatFormatting.YELLOW + "'s name history on namemc.com"))))); - - Friend bestFriend = getBestFriend(friend.getUuid()); - if (!bestFriend.equals(Friend.FRIEND_NOT_FOUND)) { - bestFriend.setName(newName); - bestFriend.setLastChecked(System.currentTimeMillis()); - } - } - if (isCommandTriggered) { - saveBestFriends(); - } else { - int remainingFriendsToCheck = bestFriendNameCheckingQueue.decrementAndGet(); - if (remainingFriendsToCheck == 0) { - // we're done with checking for name changes, save updates to file! - saveBestFriends(); - } - } - }); - } - - public void runBestFriendsOnlineCheck(boolean isCommandTriggered) { - long now = System.currentTimeMillis(); - int delay = (isCommandTriggered ? 5 : 1) * 60000; - if (nextBestFriendOnlineCheck < now) { - // ^ prevent too frequent checks - nextBestFriendOnlineCheck = now + delay; - bestFriendOnlineStatusQueue.set(0); - bestFriendsOnlineStatusWithApiErrors.clear(); - final Map onlineBestFriends = new ConcurrentHashMap<>(); - - main.getLogger().info("Checking best friends online status... (might take a bit)"); - - for (Friend bestFriend : bestFriends) { - bestFriendOnlineStatusQueue.incrementAndGet(); - ApiUtils.fetchHyPlayerDetails(bestFriend, hyPlayerData -> { - if (hyPlayerData != null && !hyPlayerData.hasNeverLoggedOut() && hyPlayerData.getLastLogin() > hyPlayerData.getLastLogout()) { - // online & not hiding their online status - main.getPlayerCache().addBestFriend(bestFriend.getName()); - - onlineBestFriends.put(bestFriend.getName(), hyPlayerData); - } - - int remainingFriendsToCheck = bestFriendOnlineStatusQueue.decrementAndGet(); - if (remainingFriendsToCheck == 0 && Minecraft.getMinecraft().thePlayer != null) { - // we're done with checking for online status - MooChatComponent bestFriendsComponent = new MooChatComponent("⬤ Online best friends (" - + EnumChatFormatting.DARK_GREEN + onlineBestFriends.size() + EnumChatFormatting.GREEN + "/" + EnumChatFormatting.DARK_GREEN + bestFriends.size() + EnumChatFormatting.GREEN + "): ").green(); - if (onlineBestFriends.isEmpty()) { - bestFriendsComponent.appendText("none..."); - } else { - TreeMap onlineBestFriendsSorted = new TreeMap<>(onlineBestFriends); - for (Map.Entry bestFriendData : onlineBestFriendsSorted.entrySet()) { - if (bestFriendsComponent.getSiblings().size() > 0) { - bestFriendsComponent.appendSibling(new MooChatComponent(", ").green()); - } - HyPlayerData hyBestFriendData = bestFriendData.getValue(); - Pair lastOnline = Utils.getDurationAsWords(hyBestFriendData.getLastLogin()); - bestFriendsComponent.appendSibling(new MooChatComponent(bestFriendData.getKey()).darkGreen() - .setHover(new MooChatComponent(hyBestFriendData.getLastGame()).yellow().appendFreshSibling(new MooChatComponent("Online for " + (lastOnline.second() != null ? lastOnline.second() : lastOnline.first())).white()))); - } - } - if (bestFriendsOnlineStatusWithApiErrors.size() > 0) { - String bestFriendsWithApiErrors = String.join(EnumChatFormatting.RED + ", " + EnumChatFormatting.DARK_RED, bestFriendsOnlineStatusWithApiErrors); - bestFriendsComponent.appendFreshSibling(new MooChatComponent("Failed to check " + EnumChatFormatting.DARK_RED + bestFriendsOnlineStatusWithApiErrors.size() + EnumChatFormatting.RED + " best friends' online status due to Hypixel API errors: " + EnumChatFormatting.DARK_RED + bestFriendsWithApiErrors).red()); - bestFriendsOnlineStatusWithApiErrors.clear(); - } - if (isCommandTriggered) { - main.getChatHelper().sendMessage(bestFriendsComponent); - } else { - // delay by 4 seconds so the message doesn't get buried due to the server welcome messages - new TickDelay(() -> main.getChatHelper().sendMessage(bestFriendsComponent), 80); - } - } - }); - } - } 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); - } + public Set getBestFriends() { + return bestFriends.stream().map(Friend::getName).collect(Collectors.toCollection(TreeSet::new)); } - public void addErroredApiRequest(String playerName) { - bestFriendsOnlineStatusWithApiErrors.add(playerName); + public int getBestFriendsListSize() { + return bestFriends.size(); } public synchronized void saveBestFriends() { @@ -222,10 +66,7 @@ public class FriendsHandler { private void loadBestFriends() { try { - boolean createdNewFile = this.bestFriendsFile.createNewFile(); - - this.bestFriends.clear(); - if (!createdNewFile) { + if (this.bestFriendsFile.exists()) { String bestFriendsData = FileUtils.readFileToString(this.bestFriendsFile, StandardCharsets.UTF_8); if (bestFriendsData.length() > 0) { this.bestFriends.addAll(parseJson(bestFriendsData)); diff --git a/src/main/java/de/cowtipper/cowlection/handler/PlayerCache.java b/src/main/java/de/cowtipper/cowlection/handler/PlayerCache.java index 6eb7904..caae8d5 100644 --- a/src/main/java/de/cowtipper/cowlection/handler/PlayerCache.java +++ b/src/main/java/de/cowtipper/cowlection/handler/PlayerCache.java @@ -8,7 +8,7 @@ import java.util.TreeSet; @SuppressWarnings("UnstableApiUsage") public class PlayerCache { private final EvictingQueue nameCache = EvictingQueue.create(50); - private final EvictingQueue bestFriendCache = EvictingQueue.create(100); + private final EvictingQueue friendCache = EvictingQueue.create(100); public PlayerCache() { } @@ -19,25 +19,25 @@ public class PlayerCache { nameCache.add(name); } - public void addBestFriend(String name) { + public void addFriend(String name) { // remove old entry (if exists) to 'push' name to the end of the queue - bestFriendCache.remove(name); - bestFriendCache.add(name); + friendCache.remove(name); + friendCache.add(name); } - public void removeBestFriend(String name) { - bestFriendCache.remove(name); + public void removeFriend(String name) { + friendCache.remove(name); } public SortedSet getAllNamesSorted() { SortedSet nameList = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - nameList.addAll(bestFriendCache); + nameList.addAll(friendCache); nameList.addAll(nameCache); return nameList; } public void clearAllCaches() { nameCache.clear(); - bestFriendCache.clear(); + friendCache.clear(); } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java index 243aa4a..83e7cb1 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java @@ -9,11 +9,9 @@ import de.cowtipper.cowlection.util.ApiUtils; import de.cowtipper.cowlection.util.MooChatComponent; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; -import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.gui.GuiChat; import net.minecraft.client.gui.GuiControls; import net.minecraft.client.gui.GuiNewChat; -import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; import net.minecraftforge.client.ClientCommandHandler; @@ -31,12 +29,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class ChatListener { - /** - * Examples: - * - §aFriend > §r§aNAME §r§eleft.§r - * - §2Guild > §r§aNAME §r§eleft.§r - */ - private static final Pattern LOGIN_LOGOUT_NOTIFICATION = Pattern.compile("^(?§aFriend|§2Guild) > §r(?§[0-9a-f])(?\\w+)(? §r§e(?joined|left)\\.)§r$"); + private static final Pattern FRIEND_LOGIN_LOGOUT_NOTIFICATION = Pattern.compile("^Friend > (?\\w+) (?joined|left)\\.$"); private static final Pattern CHAT_MESSAGE_RECEIVED_PATTERN = Pattern.compile("^(?:Party|Guild) > (?:\\[.*?] )?(\\w+)(?: \\[.*?])?: "); private static final Pattern PRIVATE_MESSAGE_RECEIVED_PATTERN = Pattern.compile("^From (?:\\[.*?] )?(\\w+): "); private static final Pattern PARTY_OR_GAME_INVITE_PATTERN = Pattern.compile("^-+\\s+(?:\\[.*?] )?(\\w+) has invited you "); @@ -49,60 +42,6 @@ public class ChatListener { this.main = main; } - @SubscribeEvent - public void onLogInOutMessage(ClientChatReceivedEvent e) { - if (e.type != 2) { // normal chat or system msg (not above action bar) - String text = e.message.getUnformattedText(); - Matcher notificationMatcher = LOGIN_LOGOUT_NOTIFICATION.matcher(e.message.getFormattedText()); - - if (MooConfig.doMonitorNotifications() && text.length() < 42 && notificationMatcher.matches()) { - // we got a login or logout notification! - main.getLogger().info(text); - - String type = notificationMatcher.group("type"); - String rank = notificationMatcher.group("rank"); - String playerName = notificationMatcher.group("playerName"); - String joinLeaveSuffix = notificationMatcher.group("joinLeaveSuffix"); - String joinedLeft = notificationMatcher.group("joinedLeft"); - - boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, false); - if (isBestFriend) { - switch (joinedLeft) { - case "joined": - main.getPlayerCache().addBestFriend(playerName); - if (MooConfig.enableBestFriendNotificationSound && Minecraft.getMinecraft().thePlayer != null) { - Minecraft.getMinecraft().thePlayer.playSound("random.pop", Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER), 1); - } - break; - case "left": - main.getPlayerCache().removeBestFriend(playerName); - break; - default: - // player neither left nor joined?! - return; - } - if (MooConfig.showBestFriendNotifications) { - // replace default (friend/guild) notification with best friend notification - e.message = new ChatComponentText("" + EnumChatFormatting.DARK_GREEN + EnumChatFormatting.BOLD + "Best friend" + EnumChatFormatting.DARK_GREEN + " > " + EnumChatFormatting.RESET + rank + playerName + joinLeaveSuffix); - return; - } - } - if (!MooConfig.showFriendNotifications && "§aFriend".equals(type)) { - e.setCanceled(true); - } else if (!MooConfig.showGuildNotifications && "§2Guild".equals(type)) { - e.setCanceled(true); - } - } else if (text.length() == 56 && text.startsWith("Your new API key is ")) { - // Your new API key is 00000000-0000-0000-0000-000000000000 - String moo = text.substring(20, 56); - if (Utils.isValidUuid(moo)) { - main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "[" + Cowlection.MODNAME + "] Verifying the new API key..."); - main.getMoo().setMooIfValid(moo, true); - } - } - } - } - @SubscribeEvent public void onClickOnChat(GuiScreenEvent.MouseInputEvent.Pre e) { if (Mouse.getEventButton() < 0) { @@ -159,45 +98,59 @@ public class ChatListener { // priority = highest to ignore other mods modifying the chat output @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) public void onChatMsgReceive(ClientChatReceivedEvent e) { - if (e.type != 2) { - String messageSender = null; - - String message = EnumChatFormatting.getTextWithoutFormattingCodes(e.message.getUnformattedText()); + if (e.type == 2) return; // above action bar + String message = EnumChatFormatting.getTextWithoutFormattingCodes(e.message.getUnformattedText()); + if (message.length() < 42) { + Matcher notificationMatcher = FRIEND_LOGIN_LOGOUT_NOTIFICATION.matcher(message); + if (notificationMatcher.matches()) { + // we got a friend login or logout notification! + String playerName = notificationMatcher.group("playerName"); + String joinedLeft = notificationMatcher.group("joinedLeft"); - Matcher privateMessageMatcher = PRIVATE_MESSAGE_RECEIVED_PATTERN.matcher(message); - Matcher chatMessageMatcher = CHAT_MESSAGE_RECEIVED_PATTERN.matcher(message); - Matcher partyOrGameInviteMatcher = PARTY_OR_GAME_INVITE_PATTERN.matcher(message); - Matcher dungeonPartyFinderJoinedMatcher = DUNGEON_FINDER_JOINED_PATTERN.matcher(message); - if (privateMessageMatcher.find()) { - messageSender = privateMessageMatcher.group(1); - if (!"stash".equals(messageSender)) { - this.lastPMSender = messageSender; - } - } else if (chatMessageMatcher.find()) { - messageSender = chatMessageMatcher.group(1); - } else if (partyOrGameInviteMatcher.find()) { - messageSender = partyOrGameInviteMatcher.group(1); - } else if (dungeonPartyFinderJoinedMatcher.find()) { - messageSender = dungeonPartyFinderJoinedMatcher.group(1); - if (CredentialStorage.isMooValid) { - boolean joinedYourself = messageSender.equals(Minecraft.getMinecraft().thePlayer.getName()); - if (!joinedYourself && MooConfig.getDungPartyFinderPlayerLookupDisplay() != MooConfig.Setting.DISABLED) { - // another player joined via Dungeon Party Finder - String dungeonClass = dungeonPartyFinderJoinedMatcher.group(2) + " Lvl " + dungeonPartyFinderJoinedMatcher.group(3); - getNewDungeonPartyMemberDetails(messageSender, dungeonClass); - } else if (joinedYourself && MooConfig.dungPartyFinderPartyLookup) { - // successfully joined another party via Dungeon Party Finder - main.getDungeonCache().lookupPartyMembers(); - } + if ("joined".equals(joinedLeft)) { + main.getPlayerCache().addFriend(playerName); + } else { // left + main.getPlayerCache().removeFriend(playerName); } - } else if (CredentialStorage.isMooValid && MooConfig.dungPartyFullLookup && message.equals("Party Finder > Your dungeon group is full! Click here to warp to the dungeon!") - && (Minecraft.getMinecraft().currentScreen == null || Minecraft.getMinecraft().currentScreen instanceof GuiChat)) { - ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, "/moo dp"); + return; } + } - if (messageSender != null) { - main.getPlayerCache().add(messageSender); + String messageSender = null; + + Matcher privateMessageMatcher = PRIVATE_MESSAGE_RECEIVED_PATTERN.matcher(message); + Matcher chatMessageMatcher = CHAT_MESSAGE_RECEIVED_PATTERN.matcher(message); + Matcher partyOrGameInviteMatcher = PARTY_OR_GAME_INVITE_PATTERN.matcher(message); + Matcher dungeonPartyFinderJoinedMatcher = DUNGEON_FINDER_JOINED_PATTERN.matcher(message); + if (privateMessageMatcher.find()) { + messageSender = privateMessageMatcher.group(1); + if (!"stash".equals(messageSender)) { + this.lastPMSender = messageSender; } + } else if (chatMessageMatcher.find()) { + messageSender = chatMessageMatcher.group(1); + } else if (partyOrGameInviteMatcher.find()) { + messageSender = partyOrGameInviteMatcher.group(1); + } else if (dungeonPartyFinderJoinedMatcher.find()) { + messageSender = dungeonPartyFinderJoinedMatcher.group(1); + if (CredentialStorage.isMooValid) { + boolean joinedYourself = messageSender.equals(Minecraft.getMinecraft().thePlayer.getName()); + if (!joinedYourself && MooConfig.getDungPartyFinderPlayerLookupDisplay() != MooConfig.Setting.DISABLED) { + // another player joined via Dungeon Party Finder + String dungeonClass = dungeonPartyFinderJoinedMatcher.group(2) + " Lvl " + dungeonPartyFinderJoinedMatcher.group(3); + getNewDungeonPartyMemberDetails(messageSender, dungeonClass); + } else if (joinedYourself && MooConfig.dungPartyFinderPartyLookup) { + // successfully joined another party via Dungeon Party Finder + main.getDungeonCache().lookupPartyMembers(); + } + } + } else if (CredentialStorage.isMooValid && MooConfig.dungPartyFullLookup && message.equals("Party Finder > Your dungeon group is full! Click here to warp to the dungeon!") + && (Minecraft.getMinecraft().currentScreen == null || Minecraft.getMinecraft().currentScreen instanceof GuiChat)) { + ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, "/moo dp"); + } + + if (messageSender != null) { + main.getPlayerCache().add(messageSender); } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java index ff29669..ad47288 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java @@ -1,9 +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.error.ApiAskPolitelyErrorEvent; import de.cowtipper.cowlection.error.ApiHttpErrorEvent; import de.cowtipper.cowlection.listener.skyblock.DungeonsListener; import de.cowtipper.cowlection.listener.skyblock.SkyBlockListener; @@ -43,6 +41,7 @@ public class PlayerListener { private boolean isOnSkyBlock; private AbortableRunnable checkScoreboard; private long nextApiErrorMessage; + private long nextMigrationNotification = 0; public PlayerListener(Cowlection main) { this.main = main; @@ -118,17 +117,9 @@ public class PlayerListener { isOnSkyBlock = false; isPlayerJoiningServer = true; main.getVersionChecker().runUpdateCheck(false); - if (MooConfig.doBestFriendsOnlineCheck && CredentialStorage.isMooValid && main.getFriendsHandler().getBestFriends().size() > 0) { - main.getFriendsHandler().runBestFriendsOnlineCheck(false); - } } } - @SubscribeEvent - public void onApiAskPolitelyError(ApiAskPolitelyErrorEvent e) { - main.getFriendsHandler().addErroredApiRequest(e.getPlayerName()); - } - @SubscribeEvent public void onApiHttpError(ApiHttpErrorEvent e) { if (nextApiErrorMessage < System.currentTimeMillis() && Minecraft.getMinecraft().thePlayer != null) { @@ -148,6 +139,26 @@ public class PlayerListener { public void onWorldEnter(PlayerSetSpawnEvent e) { isPlayerJoiningServer = false; + if (this.nextMigrationNotification < System.currentTimeMillis()) { + this.nextMigrationNotification = System.currentTimeMillis() + 10000; + new TickDelay(() -> { + if (MooConfig.doBestFriendsOnlineCheck || main.getFriendsHandler().getBestFriendsListSize() > 0) { + main.getChatHelper().sendMessage(new MooChatComponent("[" + EnumChatFormatting.DARK_RED + Cowlection.MODNAME + EnumChatFormatting.RED + "] The 'best friends list' feature has been removed from this mod.").red() + .appendSibling(new MooChatComponent(" Run " + EnumChatFormatting.GOLD + "/moo bestfriends " + EnumChatFormatting.YELLOW + "to migrate your best friends list").yellow()) + .setSuggestCommand("/moo bestfriends", false) + .setHover(new MooChatComponent.KeyValueChatComponent("Run", "/moo bestfriends", " ") + .appendFreshSibling(new MooChatComponent("(This message will re-appear as long as there are still names on your Cowlection best friends list)").red()))); + } + if (MooConfig.doMonitorNotifications()) { + main.getChatHelper().sendMessage(new MooChatComponent("[" + EnumChatFormatting.DARK_RED + Cowlection.MODNAME + EnumChatFormatting.RED + "] The 'login & logout notifications filter' feature has been removed from this mod.").red() + .appendFreshSibling(new MooChatComponent("Use Hypixel's commands instead:").gold() + .appendFreshSibling(new MooChatComponent(" §6➊ §eCycle through (best) friends notifications: §6/friend notifications §7(you may need to repeat this command to get the desired setting)").yellow().setSuggestCommand("/friend notifications")) + .appendFreshSibling(new MooChatComponent(" §6➋ §eToggle Guild notifications: §6/guild notifications").yellow().setSuggestCommand("/guild notifications"))) + .appendFreshSibling(new MooChatComponent("[Do not show this message again! I updated my settings accordingly]").darkAqua().underline().setSuggestCommand("/moo I-read-the-login-logout-notification-changes"))); + } + }, 3000); + } + if (MooConfig.getEnableSkyBlockOnlyFeatures() == MooConfig.Setting.ALWAYS) { main.getLogger().info("Registering SkyBlock listeners"); isOnSkyBlock = true; diff --git a/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java b/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java index 60f6b81..b1b1160 100644 --- a/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java +++ b/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java @@ -80,7 +80,7 @@ public class RuleEditorGui extends GuiScreen { this.buttonList.add(this.btnClose = new GuiButtonExt(2, this.width - 25, 4, 20, 20, EnumChatFormatting.RED + "X")); updateLastScrollDistance(); - // scrollable commands list + // scrollable gui rulesList = new RulesListGui(this.width - 30, this.rules.getRules(), lastScrollDistance); } diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java index 1397a36..d820c83 100644 --- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java @@ -1,7 +1,5 @@ package de.cowtipper.cowlection.util; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import com.mojang.util.UUIDTypeAdapter; import de.cowtipper.cowlection.Cowlection; @@ -11,7 +9,6 @@ import de.cowtipper.cowlection.chesttracker.data.LowestBinsCache; import de.cowtipper.cowlection.command.exception.ThrowingConsumer; import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.data.*; -import de.cowtipper.cowlection.error.ApiAskPolitelyErrorEvent; import de.cowtipper.cowlection.error.ApiHttpErrorEvent; import de.cowtipper.cowlection.error.ApiHttpErrorException; import net.minecraft.util.EnumChatFormatting; @@ -32,9 +29,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ApiUtils { - public static final String UUID_NOT_FOUND = "UUID-NOT-FOUND"; private static final String NAME_TO_UUID_URL = "https://api.mojang.com/users/profiles/minecraft/"; - private static final String UUID_TO_NAME_URL = "https://sessionserver.mojang.com/session/minecraft/profile/%s"; 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 BAZAAR_URL = "https://api.hypixel.net/skyblock/bazaar"; @@ -64,26 +59,6 @@ public class ApiUtils { return null; } - public static void fetchCurrentName(Friend friend, ThrowingConsumer action) { - pool.execute(() -> action.accept(getCurrentName(friend))); - } - - private static String getCurrentName(Friend friend) { - try (BufferedReader reader = makeApiCall(String.format(UUID_TO_NAME_URL, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { - if (reader == null) { - return UUID_NOT_FOUND; - } else { - JsonObject profile = new JsonParser().parse(reader).getAsJsonObject(); - if (profile.has("name")) { - return profile.get("name").getAsString(); - } - } - } catch (IOException | JsonSyntaxException e) { - handleApiException(e); - } - return null; - } - public static void fetchPlayerStatus(Friend friend, ThrowingConsumer action) { pool.execute(() -> action.accept(stalkPlayer(friend))); } @@ -169,8 +144,6 @@ public class ApiUtils { return GsonUtils.fromJson(reader, HyPlayerData.class); } } catch (IOException | JsonSyntaxException e) { - ApiAskPolitelyErrorEvent event = new ApiAskPolitelyErrorEvent(stalkedPlayer.getName()); - MinecraftForge.EVENT_BUS.post(event); handleApiException(e); } return null; diff --git a/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java b/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java index ea313c0..4ccb0fd 100644 --- a/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java +++ b/src/main/java/de/cowtipper/cowlection/util/VersionChecker.java @@ -72,7 +72,7 @@ public class VersionChecker { statusMsg = new ChatComponentText("✔ You're running the latest version (" + Cowlection.VERSION + ").").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GREEN)); } else if (versionResult.status == ForgeVersion.Status.PENDING) { // pending - statusMsg = new ChatComponentText("➜ " + "Version check either failed or is still running.").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW)) + statusMsg = new ChatComponentText("➜ Version check either failed or is still running.").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW)) .appendSibling(new ChatComponentText("\n ➊ Check for results again in a few seconds with " + EnumChatFormatting.GOLD + "/moo version").setChatStyle(new ChatStyle() .setColor(EnumChatFormatting.YELLOW) .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo version")) diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang index 5634376..aa95c48 100644 --- a/src/main/resources/assets/cowlection/lang/en_US.lang +++ b/src/main/resources/assets/cowlection/lang/en_US.lang @@ -1,5 +1,5 @@ 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.isMooValid.tooltip=You can use §e/moo apikey §rto set a new API key\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.mooCmdAlias=Alias for /moo command @@ -28,16 +28,6 @@ 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 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.\n§7Best friends list commands:\n ‣ §e/moo add\n ‣ §e/moo remove\n ‣ §e/moo list -cowlection.config.enableBestFriendNotificationSound=Play sound when best friend joins -cowlection.config.enableBestFriendNotificationSound.tooltip=If enabled: plays a notification sound when a best friend comes online. -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 §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§aHover over a player name to see their current gamemode.\n§d§l⚷ §eRequires a valid API key! cowlection.config.enableSkyBlockOnlyFeatures=Enable SkyBlock-only features cowlection.config.enableSkyBlockOnlyFeatures.tooltip=§6When should SkyBlock-only features be active?\n§7§o(relog or change worlds after changing this option)\n §d➊ §fonly while being on SkyBlock §e(= disabled on other servers and in other gamemodes\n §d➋ §falways §e(= including other servers and gamemodes)\n §d➌ §fnever §e(§cNone §eof the SkyBlock-only features will work!) cowlection.config.notifyFreshServer=Notify when world is loaded