aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--README.md2
-rw-r--r--src/main/java/de/cowtipper/cowlection/command/MooCommand.java182
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java2
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java76
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/gui/MooConfigBestFriendsMigration.java409
-rw-r--r--src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java15
-rw-r--r--src/main/java/de/cowtipper/cowlection/handler/FriendsHandler.java195
-rw-r--r--src/main/java/de/cowtipper/cowlection/handler/PlayerCache.java16
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/ChatListener.java145
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java31
-rw-r--r--src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java2
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java27
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/VersionChecker.java2
-rw-r--r--src/main/resources/assets/cowlection/lang/en_US.lang12
15 files changed, 610 insertions, 518 deletions
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 <player name>`
+- 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` &rarr; Notifications |
| Show all client-side commands added by all installed mods | `/commandslist` |
| Copy chat component | <kbd>ALT</kbd> + <kbd>right click</kbd><br>Hold <kbd>shift</kbd> to copy full component |
| Copy inventories to clipboard as JSON | <kbd>CTRL</kbd> + <kbd>C</kbd> (single item)<br><kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>C</kbd> (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 <key>" + 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 <key>" + EnumChatFormatting.RED + " to manually set your existing API key.");
}
if (args.length != 2) {
throw new WrongUsageException("/" + getCommandName() + " stalk <playerName>");
@@ -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 <playerName>");
- } 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 <name> " + 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<String> 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 <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>");
- } 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 <key>" + 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 <key>" + EnumChatFormatting.RED + " to manually set your existing API key.");
}
if (args.length != 2) {
throw new WrongUsageException("/" + getCommandName() + " skyblockstalk <playerName>");
@@ -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 <key>" + 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 <key>" + 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 <key>" + color + " to manually set your existing API key.");
+ main.getChatHelper().sendMessage(color, firstSentence + color + " Use " + colorSecondary + "/" + this.getCommandName() + " apikey <key>" + 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 <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 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 {
+ /**
+ * <pre>
+ * §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
+ * </pre>
+ */
+ 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 <player name>").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<String> 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<String> 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<IChatComponent> introduction;
+ private final List<BestFriendEntry> 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<BestFriendEntry> convertToEntries() {
+ Set<String> 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<IChatComponent> 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<String> btnAddTooltip;
+ private final GuiButton btnRemove;
+ private final List<String> 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<Friend> bestFriends = new ConcurrentSet<>();
private final File bestFriendsFile;
- private final AtomicInteger bestFriendNameCheckingQueue = new AtomicInteger();
- private final AtomicInteger bestFriendOnlineStatusQueue = new AtomicInteger();
- private final Set<String> 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<String> 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<String, HyPlayerData> 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<String, HyPlayerData> onlineBestFriendsSorted = new TreeMap<>(onlineBestFriends);
- for (Map.Entry<String, HyPlayerData> bestFriendData : onlineBestFriendsSorted.entrySet()) {
- if (bestFriendsComponent.getSiblings().size() > 0) {
- bestFriendsComponent.appendSibling(new MooChatComponent(", ").green());
- }
- HyPlayerData hyBestFriendData = bestFriendData.getValue();
- Pair<String, String> 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<String> 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<String> nameCache = EvictingQueue.create(50);
- private final EvictingQueue<String> bestFriendCache = EvictingQueue.create(100);
+ private final EvictingQueue<String> 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<String> getAllNamesSorted() {
SortedSet<String> 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("^(?<type>§aFriend|§2Guild) > §r(?<rank>§[0-9a-f])(?<playerName>\\w+)(?<joinLeaveSuffix> §r§e(?<joinedLeft>joined|left)\\.)§r$");
+ private static final Pattern FRIEND_LOGIN_LOGOUT_NOTIFICATION = Pattern.compile("^Friend > (?<playerName>\\w+) (?<joinedLeft>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 ");
@@ -50,60 +43,6 @@ public class ChatListener {
}
@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) {
// no button press, just mouse-hover
@@ -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,18 +117,10 @@ 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) {
this.nextApiErrorMessage = System.currentTimeMillis() + 3000;
@@ -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<String> 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<HyStalkingData> 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 <new key> §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 <X days