From 4d45e0bc9afacc8408295aef50b8fd6530f97104 Mon Sep 17 00:00:00 2001 From: Cow Date: Mon, 4 May 2020 13:25:39 +0200 Subject: Re-organized packages and files --- .../eu/olli/cowmoonication/Cowmoonication.java | 12 +- .../eu/olli/cowmoonication/command/MooCommand.java | 22 +-- .../java/eu/olli/cowmoonication/data/Friend.java | 65 ++++++++ .../olli/cowmoonication/data/HyStalkingData.java | 168 +++++++++++++++++++++ .../cowmoonication/data/SlothStalkingData.java | 48 ++++++ .../eu/olli/cowmoonication/friends/Friend.java | 65 -------- .../eu/olli/cowmoonication/friends/Friends.java | 167 -------------------- .../cowmoonication/handler/FriendsHandler.java | 168 +++++++++++++++++++++ .../olli/cowmoonication/handler/PlayerCache.java | 47 ++++++ .../olli/cowmoonication/listener/ChatListener.java | 2 +- .../cowmoonication/listener/PlayerListener.java | 2 +- .../java/eu/olli/cowmoonication/util/ApiUtils.java | 19 ++- .../eu/olli/cowmoonication/util/HyStalking.java | 167 -------------------- .../eu/olli/cowmoonication/util/PlayerCache.java | 47 ------ .../eu/olli/cowmoonication/util/SlothStalking.java | 48 ------ 15 files changed, 524 insertions(+), 523 deletions(-) create mode 100644 src/main/java/eu/olli/cowmoonication/data/Friend.java create mode 100644 src/main/java/eu/olli/cowmoonication/data/HyStalkingData.java create mode 100644 src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java delete mode 100644 src/main/java/eu/olli/cowmoonication/friends/Friend.java delete mode 100644 src/main/java/eu/olli/cowmoonication/friends/Friends.java create mode 100644 src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java create mode 100644 src/main/java/eu/olli/cowmoonication/handler/PlayerCache.java delete mode 100644 src/main/java/eu/olli/cowmoonication/util/HyStalking.java delete mode 100644 src/main/java/eu/olli/cowmoonication/util/PlayerCache.java delete mode 100644 src/main/java/eu/olli/cowmoonication/util/SlothStalking.java (limited to 'src/main/java') diff --git a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java index e44ab23..ae61781 100644 --- a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java +++ b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java @@ -4,11 +4,11 @@ import eu.olli.cowmoonication.command.MooCommand; import eu.olli.cowmoonication.command.ShrugCommand; import eu.olli.cowmoonication.command.TabCompletableCommand; import eu.olli.cowmoonication.config.MooConfig; -import eu.olli.cowmoonication.friends.Friends; +import eu.olli.cowmoonication.handler.FriendsHandler; import eu.olli.cowmoonication.listener.ChatListener; import eu.olli.cowmoonication.listener.PlayerListener; import eu.olli.cowmoonication.util.ChatHelper; -import eu.olli.cowmoonication.util.PlayerCache; +import eu.olli.cowmoonication.handler.PlayerCache; import eu.olli.cowmoonication.util.VersionChecker; import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.common.MinecraftForge; @@ -33,7 +33,7 @@ public class Cowmoonication { public static final String GITURL = "@GITURL@"; private File modsDir; private MooConfig config; - private Friends friends; + private FriendsHandler friendsHandler; private VersionChecker versionChecker; private ChatHelper chatHelper; private PlayerCache playerCache; @@ -48,7 +48,7 @@ public class Cowmoonication { modDir.mkdirs(); } - friends = new Friends(this, new File(modDir, "friends.json")); + friendsHandler = new FriendsHandler(this, new File(modDir, "friends.json")); config = new MooConfig(this, new Configuration(new File(modDir, MODID + ".cfg"))); chatHelper = new ChatHelper(); @@ -76,8 +76,8 @@ public class Cowmoonication { return config; } - public Friends getFriends() { - return friends; + public FriendsHandler getFriendsHandler() { + return friendsHandler; } public VersionChecker getVersionChecker() { diff --git a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java index c0a822b..8800ff2 100644 --- a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java +++ b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java @@ -4,9 +4,9 @@ import com.mojang.realmsclient.util.Pair; import eu.olli.cowmoonication.Cowmoonication; import eu.olli.cowmoonication.config.MooConfig; import eu.olli.cowmoonication.config.MooGuiConfig; -import eu.olli.cowmoonication.friends.Friend; +import eu.olli.cowmoonication.data.Friend; import eu.olli.cowmoonication.util.ApiUtils; -import eu.olli.cowmoonication.util.HyStalking; +import eu.olli.cowmoonication.data.HyStalkingData; import eu.olli.cowmoonication.util.TickDelay; import eu.olli.cowmoonication.util.Utils; import net.minecraft.client.Minecraft; @@ -53,7 +53,7 @@ public class MooCommand extends CommandBase { handleListBestFriends(); } else if (args[0].equalsIgnoreCase("nameChangeCheck")) { main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "Looking for best friends that have changed their name... This will take a few seconds..."); - main.getFriends().updateBestFriends(true); + main.getFriendsHandler().updateBestFriends(true); } // sub-commands: miscellaneous else if (args[0].equalsIgnoreCase("config") || args[0].equalsIgnoreCase("toggle")) { @@ -147,9 +147,9 @@ public class MooCommand extends CommandBase { return; } main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Stalking " + EnumChatFormatting.WHITE + playerName + EnumChatFormatting.GRAY + ". This may take a few seconds."); - boolean isBestFriend = main.getFriends().isBestFriend(playerName, true); + boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, true); if (isBestFriend) { - Friend stalkedPlayer = main.getFriends().getBestFriend(playerName); + Friend stalkedPlayer = main.getFriendsHandler().getBestFriend(playerName); // we have the uuid already, so stalk the player stalkPlayer(stalkedPlayer); } else { @@ -170,7 +170,7 @@ public class MooCommand extends CommandBase { private void stalkPlayer(Friend stalkedPlayer) { ApiUtils.fetchPlayerStatus(stalkedPlayer, hyStalking -> { if (hyStalking != null && hyStalking.isSuccess()) { - HyStalking.HySession session = hyStalking.getSession(); + HyStalkingData.HySession session = hyStalking.getSession(); if (session.isOnline()) { main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, EnumChatFormatting.GOLD + stalkedPlayer.getName() + EnumChatFormatting.YELLOW + " is currently playing " + EnumChatFormatting.GOLD + session.getGameType() + EnumChatFormatting.YELLOW + (session.getMode() != null ? ": " + EnumChatFormatting.GOLD + session.getMode() : "") @@ -214,12 +214,12 @@ public class MooCommand extends CommandBase { } // TODO Add check if 'best friend' is on normal friend list - if (main.getFriends().isBestFriend(username, true)) { + if (main.getFriendsHandler().isBestFriend(username, true)) { main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " is a best friend already."); } else { main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "Fetching " + EnumChatFormatting.YELLOW + username + EnumChatFormatting.GOLD + "'s unique user id. This may take a few seconds..."); // add friend async - main.getFriends().addBestFriend(username); + main.getFriendsHandler().addBestFriend(username); } } @@ -229,7 +229,7 @@ public class MooCommand extends CommandBase { return; } - boolean removed = main.getFriends().removeBestFriend(username); + boolean removed = main.getFriendsHandler().removeBestFriend(username); if (removed) { main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Removed " + EnumChatFormatting.DARK_GREEN + username + EnumChatFormatting.GREEN + " from best friends list."); } else { @@ -238,7 +238,7 @@ public class MooCommand extends CommandBase { } private void handleListBestFriends() { - Set bestFriends = main.getFriends().getBestFriends(); + 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, "\u279C Best friends: " + EnumChatFormatting.DARK_GREEN + String.join(EnumChatFormatting.GREEN + ", " + EnumChatFormatting.DARK_GREEN, bestFriends)); @@ -303,7 +303,7 @@ public class MooCommand extends CommandBase { /* update mod */ "update", "updateHelp", "version", "folder", /* help */ "help"); } else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { - return getListOfStringsMatchingLastWord(args, main.getFriends().getBestFriends()); + return getListOfStringsMatchingLastWord(args, main.getFriendsHandler().getBestFriends()); } else if (args.length == 2 && args[0].equalsIgnoreCase("stalk")) { return getListOfStringsMatchingLastWord(args, main.getPlayerCache().getAllNamesSorted()); } diff --git a/src/main/java/eu/olli/cowmoonication/data/Friend.java b/src/main/java/eu/olli/cowmoonication/data/Friend.java new file mode 100644 index 0000000..69741e2 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/data/Friend.java @@ -0,0 +1,65 @@ +package eu.olli.cowmoonication.data; + +import java.util.Objects; +import java.util.UUID; + +public class Friend { + public static final Friend FRIEND_NOT_FOUND = new Friend(); + private UUID id; + private String name; + private long lastChecked; + + static { + // uuid & name are null + FRIEND_NOT_FOUND.setLastChecked(0); + } + + /** + * No-args constructor for GSON + */ + private Friend() { + this.lastChecked = System.currentTimeMillis(); + } + + public UUID getUuid() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getLastChecked() { + return lastChecked; + } + + public void setLastChecked(long lastChecked) { + this.lastChecked = lastChecked; + } + + @Override + public String toString() { + return "Friend{" + + "uuid=" + id + + ", name='" + name + '\'' + + ", lastChecked=" + lastChecked + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Friend friend = (Friend) o; + return Objects.equals(id, friend.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/src/main/java/eu/olli/cowmoonication/data/HyStalkingData.java b/src/main/java/eu/olli/cowmoonication/data/HyStalkingData.java new file mode 100644 index 0000000..fdb5543 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/data/HyStalkingData.java @@ -0,0 +1,168 @@ +package eu.olli.cowmoonication.data; + +import eu.olli.cowmoonication.util.Utils; +import org.apache.commons.lang3.StringUtils; + +public class HyStalkingData { + private boolean success; + private String cause; + private HySession session; + + /** + * No-args constructor for GSON + */ + private HyStalkingData() { + } + + public boolean isSuccess() { + return success; + } + + public String getCause() { + return cause; + } + + public HySession getSession() { + return session; + } + + public static class HySession { + private boolean online; + private String gameType; + private String mode; + private String map; + + /** + * No-args constructor for GSON + */ + private HySession() { + } + + public boolean isOnline() { + return online; + } + + public String getGameType() { + String cleanGameType; + try { + cleanGameType = GameType.valueOf(gameType).getCleanName(); + } catch (IllegalArgumentException e) { + // no matching game type found + cleanGameType = Utils.fancyCase(gameType); + } + return cleanGameType; + } + + public String getMode() { + // modes partially taken from https://api.hypixel.net/gameCounts?key=MOO + if (mode == null) { + return null; + } + String gameType = getGameType(); + if (GameType.BEDWARS.cleanName.equals(gameType)) { + // BedWars related + String playerMode; + String specialMode; + int specialModeStart = StringUtils.ordinalIndexOf(mode, "_", 2); + if (specialModeStart > -1) { + playerMode = mode.substring(0, specialModeStart); + specialMode = mode.substring(specialModeStart + 1) + " "; + } else { + playerMode = mode; + specialMode = ""; + } + String playerModeClean; + switch (playerMode) { + case "EIGHT_ONE": + playerModeClean = "Solo"; + break; + case "EIGHT_TWO": + playerModeClean = "Doubles"; + break; + case "FOUR_THREE": + playerModeClean = "3v3v3v3"; + break; + case "FOUR_FOUR": + playerModeClean = "4v4v4v4"; + break; + case "TWO_FOUR": + playerModeClean = "4v4"; + break; + default: + playerModeClean = playerMode; + } + return Utils.fancyCase(specialMode + playerModeClean); + } else if (GameType.SKYBLOCK.cleanName.equals(gameType)) { + // SkyBlock related + switch (mode) { + case "dynamic": + return "Private Island"; + case "hub": + return "Hub"; + case "combat_1": + return "Spider's Den"; + case "combat_2": + return "Blazing Fortress"; + case "combat_3": + return "The End"; + case "farming_1": + return "The Barn"; + case "farming_2": + return "Mushroom Desert"; + case "foraging_1": + return "The Park"; + case "mining_1": + return "Gold Mine"; + case "mining_2": + return "Deep Caverns"; + } + } + return Utils.fancyCase(mode); + } + + public String getMap() { + return map; + } + + // TODO replace with api request: https://github.com/HypixelDev/PublicAPI/blob/master/Documentation/misc/GameType.md + public enum GameType { + QUAKECRAFT("Quakecraft"), + WALLS("Walls"), + PAINTBALL("Paintball"), + SURVIVAL_GAMES("Blitz Survival Games"), + TNTGAMES("The TNT Games"), + VAMPIREZ("VampireZ"), + WALLS3("Mega Walls"), + ARCADE("Arcade"), + ARENA("Arena Brawl"), + UHC("UHC Champions"), + MCGO("Cops and Crims"), + BATTLEGROUND("Warlords"), + SUPER_SMASH("Smash Heroes"), + GINGERBREAD("Turbo Kart Racers"), + HOUSING("Housing"), + SKYWARS("SkyWars"), + TRUE_COMBAT("Crazy Walls"), + SPEED_UHC("Speed UHC"), + SKYCLASH("SkyClash"), + LEGACY("Classic Games"), + PROTOTYPE("Prototype"), + BEDWARS("Bed Wars"), + MURDER_MYSTERY("Murder Mystery"), + BUILD_BATTLE("Build Battle"), + DUELS("Duels"), + SKYBLOCK("SkyBlock"), + PIT("Pit"); + + private final String cleanName; + + GameType(String cleanName) { + this.cleanName = cleanName; + } + + public String getCleanName() { + return cleanName; + } + } + } +} diff --git a/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java b/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java new file mode 100644 index 0000000..1072634 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java @@ -0,0 +1,48 @@ +package eu.olli.cowmoonication.data; + +public class SlothStalkingData { + private String username; + private String rank; + private String rank_formatted; + // private boolean online; + private long last_login; + private long last_logout; + private String last_game; + + /** + * No-args constructor for GSON + */ + public SlothStalkingData() { + } + + public String getPlayerNameFormatted() { + return rank_formatted.replace('&', '§') + " " + username; + } + + public long getLastLogin() { + return last_login; + } + + public long getLastLogout() { + return last_logout; + } + + public String getLastGame() { + return last_game; + } + + public boolean hasNeverJoinedHypixel() { + // example player that has never joined Hypixel (as of April 2020): Joe + return rank == null && last_login == 0; + } + + public boolean hasNeverLoggedOut() { + // example player that has no logout value (as of April 2020): Pig (in general accounts that haven't logged in for a few years) + return last_login != 0 && last_logout == 0; + } + + public boolean isHidingOnlineStatus() { + // example players: any higher ranked player (mods, admins, ...) + return last_login == 0 && last_logout == 0; + } +} diff --git a/src/main/java/eu/olli/cowmoonication/friends/Friend.java b/src/main/java/eu/olli/cowmoonication/friends/Friend.java deleted file mode 100644 index 88b9d87..0000000 --- a/src/main/java/eu/olli/cowmoonication/friends/Friend.java +++ /dev/null @@ -1,65 +0,0 @@ -package eu.olli.cowmoonication.friends; - -import java.util.Objects; -import java.util.UUID; - -public class Friend { - public static final Friend FRIEND_NOT_FOUND = new Friend(); - private UUID id; - private String name; - private long lastChecked; - - static { - // uuid & name are null - FRIEND_NOT_FOUND.setLastChecked(0); - } - - /** - * No-args constructor for GSON - */ - private Friend() { - this.lastChecked = System.currentTimeMillis(); - } - - public UUID getUuid() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public long getLastChecked() { - return lastChecked; - } - - public void setLastChecked(long lastChecked) { - this.lastChecked = lastChecked; - } - - @Override - public String toString() { - return "Friend{" + - "uuid=" + id + - ", name='" + name + '\'' + - ", lastChecked=" + lastChecked + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Friend friend = (Friend) o; - return Objects.equals(id, friend.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } -} diff --git a/src/main/java/eu/olli/cowmoonication/friends/Friends.java b/src/main/java/eu/olli/cowmoonication/friends/Friends.java deleted file mode 100644 index 2fb80f0..0000000 --- a/src/main/java/eu/olli/cowmoonication/friends/Friends.java +++ /dev/null @@ -1,167 +0,0 @@ -package eu.olli.cowmoonication.friends; - -import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; -import eu.olli.cowmoonication.Cowmoonication; -import eu.olli.cowmoonication.util.ApiUtils; -import eu.olli.cowmoonication.util.GsonUtils; -import eu.olli.cowmoonication.util.TickDelay; -import io.netty.util.internal.ConcurrentSet; -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 java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -public class Friends { - private static final long UPDATE_FREQUENCY_DEFAULT = TimeUnit.HOURS.toMillis(15); - private static final long UPDATE_FREQUENCY_MINIMUM = TimeUnit.MINUTES.toMillis(5); - private final Cowmoonication main; - private Set bestFriends = new ConcurrentSet<>(); - private File bestFriendsFile; - private UpdateStatus updateStatus; - - public Friends(Cowmoonication main, File friendsFile) { - this.main = main; - this.bestFriendsFile = friendsFile; - this.updateStatus = UpdateStatus.IDLE; - loadBestFriends(); - updateBestFriends(false); - } - - 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) { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Sorry, could contact Mojang's API and thus didn't add " + name + " as a best friend."); - } else if (friend.equals(Friend.FRIEND_NOT_FOUND)) { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "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) { - 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 updateBestFriends(boolean isCommandTriggered) { - bestFriends.stream().filter(friend -> System.currentTimeMillis() - friend.getLastChecked() > (isCommandTriggered ? UPDATE_FREQUENCY_MINIMUM : UPDATE_FREQUENCY_DEFAULT)).forEach(this::updateBestFriend); - - new TickDelay(() -> { - if (this.updateStatus != UpdateStatus.IDLE) { - if (isCommandTriggered && updateStatus == UpdateStatus.NO_NAME_CHANGES) { - main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "No name changes detected."); - } - saveBestFriends(); - this.updateStatus = UpdateStatus.IDLE; - } - }, 10 * 20); - } - - private void updateBestFriend(Friend friend) { - ApiUtils.fetchCurrentName(friend, newName -> { - if (newName == null) { - // skipping friend, something went wrong with API request - } else if (newName.equals(ApiUtils.UUID_NOT_FOUND)) { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "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 (this.updateStatus == UpdateStatus.IDLE) { - this.updateStatus = UpdateStatus.NO_NAME_CHANGES; - } - } - } 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()); - this.updateStatus = UpdateStatus.NAME_CHANGED; - } - } - }); - } - - public synchronized void saveBestFriends() { - try { - String bestFriendsJsonZoned = GsonUtils.toJson(this.bestFriends); - FileUtils.writeStringToFile(this.bestFriendsFile, bestFriendsJsonZoned, StandardCharsets.UTF_8); - } catch (IOException e) { - main.getLogger().error("Couldn't save best friends", e); - } - } - - private void loadBestFriends() { - try { - this.bestFriends.clear(); - String bestFriendsData = FileUtils.readFileToString(this.bestFriendsFile, StandardCharsets.UTF_8); - this.bestFriends.addAll(parseJson(bestFriendsData)); - } catch (IOException e) { - main.getLogger().error("Couldn't read best friends file " + this.bestFriendsFile, e); - } catch (JsonParseException e) { - main.getLogger().error("Couldn't parse best friends file " + this.bestFriendsFile, e); - } - } - - private Set parseJson(String bestFriendsData) { - Type collectionType = new TypeToken>() { - }.getType(); - return GsonUtils.fromJson(bestFriendsData, collectionType); - } - - private enum UpdateStatus { - IDLE, NAME_CHANGED, NO_NAME_CHANGES - } -} diff --git a/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java new file mode 100644 index 0000000..119e289 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java @@ -0,0 +1,168 @@ +package eu.olli.cowmoonication.handler; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import eu.olli.cowmoonication.Cowmoonication; +import eu.olli.cowmoonication.data.Friend; +import eu.olli.cowmoonication.util.ApiUtils; +import eu.olli.cowmoonication.util.GsonUtils; +import eu.olli.cowmoonication.util.TickDelay; +import io.netty.util.internal.ConcurrentSet; +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 java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class FriendsHandler { + private static final long UPDATE_FREQUENCY_DEFAULT = TimeUnit.HOURS.toMillis(15); + private static final long UPDATE_FREQUENCY_MINIMUM = TimeUnit.MINUTES.toMillis(5); + private final Cowmoonication main; + private Set bestFriends = new ConcurrentSet<>(); + private File bestFriendsFile; + private UpdateStatus updateStatus; + + public FriendsHandler(Cowmoonication main, File friendsFile) { + this.main = main; + this.bestFriendsFile = friendsFile; + this.updateStatus = UpdateStatus.IDLE; + loadBestFriends(); + updateBestFriends(false); + } + + 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) { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Sorry, could contact Mojang's API and thus didn't add " + name + " as a best friend."); + } else if (friend.equals(Friend.FRIEND_NOT_FOUND)) { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "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) { + 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 updateBestFriends(boolean isCommandTriggered) { + bestFriends.stream().filter(friend -> System.currentTimeMillis() - friend.getLastChecked() > (isCommandTriggered ? UPDATE_FREQUENCY_MINIMUM : UPDATE_FREQUENCY_DEFAULT)).forEach(this::updateBestFriend); + + new TickDelay(() -> { + if (this.updateStatus != UpdateStatus.IDLE) { + if (isCommandTriggered && updateStatus == UpdateStatus.NO_NAME_CHANGES) { + main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "No name changes detected."); + } + saveBestFriends(); + this.updateStatus = UpdateStatus.IDLE; + } + }, 10 * 20); + } + + private void updateBestFriend(Friend friend) { + ApiUtils.fetchCurrentName(friend, newName -> { + if (newName == null) { + // skipping friend, something went wrong with API request + } else if (newName.equals(ApiUtils.UUID_NOT_FOUND)) { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "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 (this.updateStatus == UpdateStatus.IDLE) { + this.updateStatus = UpdateStatus.NO_NAME_CHANGES; + } + } + } 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()); + this.updateStatus = UpdateStatus.NAME_CHANGED; + } + } + }); + } + + public synchronized void saveBestFriends() { + try { + String bestFriendsJsonZoned = GsonUtils.toJson(this.bestFriends); + FileUtils.writeStringToFile(this.bestFriendsFile, bestFriendsJsonZoned, StandardCharsets.UTF_8); + } catch (IOException e) { + main.getLogger().error("Couldn't save best friends", e); + } + } + + private void loadBestFriends() { + try { + this.bestFriends.clear(); + String bestFriendsData = FileUtils.readFileToString(this.bestFriendsFile, StandardCharsets.UTF_8); + this.bestFriends.addAll(parseJson(bestFriendsData)); + } catch (IOException e) { + main.getLogger().error("Couldn't read best friends file " + this.bestFriendsFile, e); + } catch (JsonParseException e) { + main.getLogger().error("Couldn't parse best friends file " + this.bestFriendsFile, e); + } + } + + private Set parseJson(String bestFriendsData) { + Type collectionType = new TypeToken>() { + }.getType(); + return GsonUtils.fromJson(bestFriendsData, collectionType); + } + + private enum UpdateStatus { + IDLE, NAME_CHANGED, NO_NAME_CHANGES + } +} diff --git a/src/main/java/eu/olli/cowmoonication/handler/PlayerCache.java b/src/main/java/eu/olli/cowmoonication/handler/PlayerCache.java new file mode 100644 index 0000000..fd1062d --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/handler/PlayerCache.java @@ -0,0 +1,47 @@ +package eu.olli.cowmoonication.handler; + +import com.google.common.collect.EvictingQueue; +import eu.olli.cowmoonication.Cowmoonication; + +import java.util.SortedSet; +import java.util.TreeSet; + +public class PlayerCache { + @SuppressWarnings("UnstableApiUsage") + private final EvictingQueue nameCache = EvictingQueue.create(50); + @SuppressWarnings("UnstableApiUsage") + private final EvictingQueue bestFriendCache = EvictingQueue.create(50); + private final Cowmoonication main; + + public PlayerCache(Cowmoonication main) { + this.main = main; + } + + public void add(String name) { + // remove old entry (if exists) to 'push' name to the end of the queue + nameCache.remove(name); + nameCache.add(name); + } + + public void addBestFriend(String name) { + // remove old entry (if exists) to 'push' name to the end of the queue + bestFriendCache.remove(name); + bestFriendCache.add(name); + } + + public void removeBestFriend(String name) { + bestFriendCache.remove(name); + } + + public SortedSet getAllNamesSorted() { + SortedSet nameList = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + nameList.addAll(bestFriendCache); + nameList.addAll(nameCache); + return nameList; + } + + public void clearAllCaches() { + nameCache.clear(); + bestFriendCache.clear(); + } +} diff --git a/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java index 855166a..c71b35c 100644 --- a/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java +++ b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java @@ -56,7 +56,7 @@ public class ChatListener { String joinedLeft = notificationMatcher.group("joinedLeft"); - boolean isBestFriend = main.getFriends().isBestFriend(playerName, false); + boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, false); if (isBestFriend) { switch (joinedLeft) { case "joined": diff --git a/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java b/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java index 95c35c6..9a443b1 100644 --- a/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java +++ b/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java @@ -20,7 +20,7 @@ public class PlayerListener { @SubscribeEvent public void onServerLeave(FMLNetworkEvent.ClientDisconnectionFromServerEvent e) { - main.getFriends().saveBestFriends(); + main.getFriendsHandler().saveBestFriends(); main.getPlayerCache().clearAllCaches(); } } diff --git a/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java index 2dc41b2..aca1819 100644 --- a/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java +++ b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java @@ -1,13 +1,13 @@ package eu.olli.cowmoonication.util; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonParser; import com.mojang.util.UUIDTypeAdapter; import eu.olli.cowmoonication.Cowmoonication; import eu.olli.cowmoonication.config.MooConfig; -import eu.olli.cowmoonication.friends.Friend; +import eu.olli.cowmoonication.data.Friend; +import eu.olli.cowmoonication.data.HyStalkingData; +import eu.olli.cowmoonication.data.SlothStalkingData; import org.apache.http.HttpStatus; import java.io.BufferedReader; @@ -16,7 +16,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; @@ -69,14 +68,14 @@ public class ApiUtils { return null; } - public static void fetchPlayerStatus(Friend friend, Consumer action) { + public static void fetchPlayerStatus(Friend friend, Consumer action) { pool.execute(() -> action.accept(stalkPlayer(friend))); } - private static HyStalking stalkPlayer(Friend friend) { + private static HyStalkingData stalkPlayer(Friend friend) { try (BufferedReader reader = makeApiCall(String.format(STALKING_URL_OFFICIAL, MooConfig.moo, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { if (reader != null) { - return GsonUtils.fromJson(reader, HyStalking.class); + return GsonUtils.fromJson(reader, HyStalkingData.class); } } catch (IOException e) { e.printStackTrace(); @@ -84,14 +83,14 @@ public class ApiUtils { return null; } - public static void fetchPlayerOfflineStatus(Friend stalkedPlayer, Consumer action) { + public static void fetchPlayerOfflineStatus(Friend stalkedPlayer, Consumer action) { pool.execute(() -> action.accept(stalkOfflinePlayer(stalkedPlayer))); } - private static SlothStalking stalkOfflinePlayer(Friend stalkedPlayer) { + private static SlothStalkingData stalkOfflinePlayer(Friend stalkedPlayer) { try (BufferedReader reader = makeApiCall(String.format(STALKING_URL_UNOFFICIAL, UUIDTypeAdapter.fromUUID(stalkedPlayer.getUuid())))) { if (reader != null) { - return GsonUtils.fromJson(reader, SlothStalking.class); + return GsonUtils.fromJson(reader, SlothStalkingData.class); } } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/eu/olli/cowmoonication/util/HyStalking.java b/src/main/java/eu/olli/cowmoonication/util/HyStalking.java deleted file mode 100644 index 19f2362..0000000 --- a/src/main/java/eu/olli/cowmoonication/util/HyStalking.java +++ /dev/null @@ -1,167 +0,0 @@ -package eu.olli.cowmoonication.util; - -import org.apache.commons.lang3.StringUtils; - -public class HyStalking { - private boolean success; - private String cause; - private HySession session; - - /** - * No-args constructor for GSON - */ - private HyStalking() { - } - - public boolean isSuccess() { - return success; - } - - public String getCause() { - return cause; - } - - public HySession getSession() { - return session; - } - - public static class HySession { - private boolean online; - private String gameType; - private String mode; - private String map; - - /** - * No-args constructor for GSON - */ - private HySession() { - } - - public boolean isOnline() { - return online; - } - - public String getGameType() { - String cleanGameType; - try { - cleanGameType = GameType.valueOf(gameType).getCleanName(); - } catch (IllegalArgumentException e) { - // no matching game type found - cleanGameType = Utils.fancyCase(gameType); - } - return cleanGameType; - } - - public String getMode() { - // modes partially taken from https://api.hypixel.net/gameCounts?key=MOO - if (mode == null) { - return null; - } - String gameType = getGameType(); - if (GameType.BEDWARS.cleanName.equals(gameType)) { - // BedWars related - String playerMode; - String specialMode; - int specialModeStart = StringUtils.ordinalIndexOf(mode, "_", 2); - if (specialModeStart > -1) { - playerMode = mode.substring(0, specialModeStart); - specialMode = mode.substring(specialModeStart + 1) + " "; - } else { - playerMode = mode; - specialMode = ""; - } - String playerModeClean; - switch (playerMode) { - case "EIGHT_ONE": - playerModeClean = "Solo"; - break; - case "EIGHT_TWO": - playerModeClean = "Doubles"; - break; - case "FOUR_THREE": - playerModeClean = "3v3v3v3"; - break; - case "FOUR_FOUR": - playerModeClean = "4v4v4v4"; - break; - case "TWO_FOUR": - playerModeClean = "4v4"; - break; - default: - playerModeClean = playerMode; - } - return Utils.fancyCase(specialMode + playerModeClean); - } else if (GameType.SKYBLOCK.cleanName.equals(gameType)) { - // SkyBlock related - switch (mode) { - case "dynamic": - return "Private Island"; - case "hub": - return "Hub"; - case "combat_1": - return "Spider's Den"; - case "combat_2": - return "Blazing Fortress"; - case "combat_3": - return "The End"; - case "farming_1": - return "The Barn"; - case "farming_2": - return "Mushroom Desert"; - case "foraging_1": - return "The Park"; - case "mining_1": - return "Gold Mine"; - case "mining_2": - return "Deep Caverns"; - } - } - return Utils.fancyCase(mode); - } - - public String getMap() { - return map; - } - - // TODO replace with api request: https://github.com/HypixelDev/PublicAPI/blob/master/Documentation/misc/GameType.md - public enum GameType { - QUAKECRAFT("Quakecraft"), - WALLS("Walls"), - PAINTBALL("Paintball"), - SURVIVAL_GAMES("Blitz Survival Games"), - TNTGAMES("The TNT Games"), - VAMPIREZ("VampireZ"), - WALLS3("Mega Walls"), - ARCADE("Arcade"), - ARENA("Arena Brawl"), - UHC("UHC Champions"), - MCGO("Cops and Crims"), - BATTLEGROUND("Warlords"), - SUPER_SMASH("Smash Heroes"), - GINGERBREAD("Turbo Kart Racers"), - HOUSING("Housing"), - SKYWARS("SkyWars"), - TRUE_COMBAT("Crazy Walls"), - SPEED_UHC("Speed UHC"), - SKYCLASH("SkyClash"), - LEGACY("Classic Games"), - PROTOTYPE("Prototype"), - BEDWARS("Bed Wars"), - MURDER_MYSTERY("Murder Mystery"), - BUILD_BATTLE("Build Battle"), - DUELS("Duels"), - SKYBLOCK("SkyBlock"), - PIT("Pit"); - - private final String cleanName; - - GameType(String cleanName) { - this.cleanName = cleanName; - } - - public String getCleanName() { - return cleanName; - } - } - } -} diff --git a/src/main/java/eu/olli/cowmoonication/util/PlayerCache.java b/src/main/java/eu/olli/cowmoonication/util/PlayerCache.java deleted file mode 100644 index 383ca03..0000000 --- a/src/main/java/eu/olli/cowmoonication/util/PlayerCache.java +++ /dev/null @@ -1,47 +0,0 @@ -package eu.olli.cowmoonication.util; - -import com.google.common.collect.EvictingQueue; -import eu.olli.cowmoonication.Cowmoonication; - -import java.util.SortedSet; -import java.util.TreeSet; - -public class PlayerCache { - @SuppressWarnings("UnstableApiUsage") - private final EvictingQueue nameCache = EvictingQueue.create(50); - @SuppressWarnings("UnstableApiUsage") - private final EvictingQueue bestFriendCache = EvictingQueue.create(50); - private final Cowmoonication main; - - public PlayerCache(Cowmoonication main) { - this.main = main; - } - - public void add(String name) { - // remove old entry (if exists) to 'push' name to the end of the queue - nameCache.remove(name); - nameCache.add(name); - } - - public void addBestFriend(String name) { - // remove old entry (if exists) to 'push' name to the end of the queue - bestFriendCache.remove(name); - bestFriendCache.add(name); - } - - public void removeBestFriend(String name) { - bestFriendCache.remove(name); - } - - public SortedSet getAllNamesSorted() { - SortedSet nameList = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - nameList.addAll(bestFriendCache); - nameList.addAll(nameCache); - return nameList; - } - - public void clearAllCaches() { - nameCache.clear(); - bestFriendCache.clear(); - } -} diff --git a/src/main/java/eu/olli/cowmoonication/util/SlothStalking.java b/src/main/java/eu/olli/cowmoonication/util/SlothStalking.java deleted file mode 100644 index 9e659ec..0000000 --- a/src/main/java/eu/olli/cowmoonication/util/SlothStalking.java +++ /dev/null @@ -1,48 +0,0 @@ -package eu.olli.cowmoonication.util; - -public class SlothStalking { - private String username; - private String rank; - private String rank_formatted; - // private boolean online; - private long last_login; - private long last_logout; - private String last_game; - - /** - * No-args constructor for GSON - */ - public SlothStalking() { - } - - public String getPlayerNameFormatted() { - return rank_formatted.replace('&', '§') + " " + username; - } - - public long getLastLogin() { - return last_login; - } - - public long getLastLogout() { - return last_logout; - } - - public String getLastGame() { - return last_game; - } - - public boolean hasNeverJoinedHypixel() { - // example player that has never joined Hypixel (as of April 2020): Joe - return rank == null && last_login == 0; - } - - public boolean hasNeverLoggedOut() { - // example player that has no logout value (as of April 2020): Pig (in general accounts that haven't logged in for a few years) - return last_login != 0 && last_logout == 0; - } - - public boolean isHidingOnlineStatus() { - // example players: any higher ranked player (mods, admins, ...) - return last_login == 0 && last_logout == 0; - } -} -- cgit