aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md9
-rw-r--r--README.md2
-rw-r--r--src/main/java/eu/olli/cowmoonication/Cowmoonication.java21
-rw-r--r--src/main/java/eu/olli/cowmoonication/Friends.java28
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/MooCommand.java89
-rw-r--r--src/main/java/eu/olli/cowmoonication/config/MooConfig.java19
-rw-r--r--src/main/java/eu/olli/cowmoonication/friends/Friend.java78
-rw-r--r--src/main/java/eu/olli/cowmoonication/friends/Friends.java167
-rw-r--r--src/main/java/eu/olli/cowmoonication/listener/ChatListener.java10
-rw-r--r--src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java7
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/ApiUtils.java70
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/ChatHelper.java74
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/Utils.java59
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/VersionChecker.java2
14 files changed, 502 insertions, 133 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14ac786..9132a76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,15 @@ 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.3.0 - unreleased
+### Added
+- `/moo nameChangeCheck`: Force a scan for changed names of best friends
+
+### Changed
+- Moved best friends add/remove functionality from config GUI back to commands (`/moo <add|remove> <name>`)
+- Saving best friends' UUIDs now (instead of just the name), also checking for name changes periodically to keep best friends list up to date
+
+
## [1.8.9-0.2.0] - 08.03.2020
### Added
- Mod update notification (opt-out via config)
diff --git a/README.md b/README.md
index 8630a93..86b43c8 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A client-side only Forge mod by [Cow](https://namemc.com/profile/Cow) providing
| Feature | Command/Usage |
|-------------------------------------------------------------------------|-----------------------------------------|
| Toggle to hide all join/leave notifications | `/moo toggle` |
-| 'Best friends' list to limit the amount of join and leave notifications | `/moo friends` |
+| 'Best friends' list to limit the amount of join and leave notifications | `/moo add/remove/list` |
| Change guiScale to any value | `/moo guiscale [newValue]` |
| Auto-replace `/r` with `/w <latest username>` | `/r ` |
| Copy chat components | <kbd>ALT</kbd> + <kbd>right click</kbd> |
diff --git a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
index 78f1905..2b46e71 100644
--- a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
+++ b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
@@ -2,9 +2,10 @@ package eu.olli.cowmoonication;
import eu.olli.cowmoonication.command.MooCommand;
import eu.olli.cowmoonication.config.MooConfig;
+import eu.olli.cowmoonication.friends.Friends;
import eu.olli.cowmoonication.listener.ChatListener;
import eu.olli.cowmoonication.listener.PlayerListener;
-import eu.olli.cowmoonication.util.Utils;
+import eu.olli.cowmoonication.util.ChatHelper;
import eu.olli.cowmoonication.util.VersionChecker;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.common.MinecraftForge;
@@ -26,10 +27,11 @@ public class Cowmoonication {
public static final String MODID = "cowmoonication";
public static final String VERSION = "1.8.9-0.2.0";
public static final String MODNAME = "Cowmoonication";
+ private File modsDir;
private MooConfig config;
private Friends friends;
private VersionChecker versionChecker;
- private Utils utils;
+ private ChatHelper chatHelper;
private Logger logger;
@Mod.EventHandler
@@ -41,10 +43,11 @@ public class Cowmoonication {
modDir.mkdirs();
}
- friends = new Friends(this);
- config = new MooConfig(new Configuration(new File(modDir, MODID + ".cfg")), this);
+ friends = new Friends(this, new File(modDir, "friends.json"));
+ config = new MooConfig(new Configuration(new File(modDir, MODID + ".cfg")));
- utils = new Utils(this, e.getSourceFile());
+ chatHelper = new ChatHelper();
+ modsDir = e.getSourceFile().getParentFile();
}
@EventHandler
@@ -72,8 +75,12 @@ public class Cowmoonication {
return versionChecker;
}
- public Utils getUtils() {
- return utils;
+ public ChatHelper getChatHelper() {
+ return chatHelper;
+ }
+
+ public File getModsFolder() {
+ return modsDir;
}
public Logger getLogger() {
diff --git a/src/main/java/eu/olli/cowmoonication/Friends.java b/src/main/java/eu/olli/cowmoonication/Friends.java
deleted file mode 100644
index 2becc74..0000000
--- a/src/main/java/eu/olli/cowmoonication/Friends.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package eu.olli.cowmoonication;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.TreeSet;
-
-public class Friends {
- private final Cowmoonication main;
- private Set<String> bestFriends = new HashSet<>();
-
- public Friends(Cowmoonication main) {
- this.main = main;
- }
-
- public boolean isBestFriend(String playerName) {
- return bestFriends.contains(playerName);
- }
-
- public Set<String> getBestFriends() {
- return new TreeSet<>(bestFriends);
- }
-
- public void syncFriends(String[] bestFriends) {
- this.bestFriends = new HashSet<>();
- Collections.addAll(this.bestFriends, bestFriends);
- }
-}
diff --git a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
index 08c377d..22d510a 100644
--- a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
+++ b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
@@ -4,6 +4,7 @@ import eu.olli.cowmoonication.Cowmoonication;
import eu.olli.cowmoonication.config.MooConfig;
import eu.olli.cowmoonication.config.MooGuiConfig;
import eu.olli.cowmoonication.util.TickDelay;
+import eu.olli.cowmoonication.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
@@ -28,40 +29,52 @@ public class MooCommand extends CommandBase {
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (args.length == 0) {
- main.getUtils().sendMessage(new ChatComponentTranslation(getCommandUsage(sender)));
+ main.getChatHelper().sendMessage(new ChatComponentTranslation(getCommandUsage(sender)));
return;
}
- if (args[0].equalsIgnoreCase("friends") || args[0].equalsIgnoreCase("config")) {
- new TickDelay(() -> Minecraft.getMinecraft().displayGuiScreen(new MooGuiConfig(null)), 1); // delay by 1 tick, because the chat closing would close the new gui instantly as well.
+ // sub commands: friends
+ if (args.length == 2 && args[0].equalsIgnoreCase("add")) {
+ handleBestFriendAdd(args[1]);
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) {
+ handleBestFriendRemove(args[1]);
} else if (args[0].equalsIgnoreCase("list")) {
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);
} else if (args[0].equalsIgnoreCase("toggle")) {
main.getConfig().toggleNotifications();
- main.getUtils().sendMessage(EnumChatFormatting.GREEN + "\u2714 Switched all non-best friend login/logout notifications " + (MooConfig.filterFriendNotifications ? EnumChatFormatting.DARK_GREEN + "off" : EnumChatFormatting.DARK_RED + "on"));
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u2714 Switched all non-best friend login/logout notifications " + (MooConfig.filterFriendNotifications ? EnumChatFormatting.DARK_GREEN + "off" : EnumChatFormatting.DARK_RED + "on"));
+ }
+ // sub-commands: miscellaneous
+ else if (args[0].equalsIgnoreCase("config")) {
+ new TickDelay(() -> Minecraft.getMinecraft().displayGuiScreen(new MooGuiConfig(null)), 1); // delay by 1 tick, because the chat closing would close the new gui instantly as well.
} else if (args[0].equalsIgnoreCase("guiscale")) {
int currentGuiScale = (Minecraft.getMinecraft()).gameSettings.guiScale;
if (args.length == 1) {
- main.getUtils().sendMessage(EnumChatFormatting.GREEN + "\u279C Current GUI scale: " + EnumChatFormatting.DARK_GREEN + currentGuiScale);
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u279C Current GUI scale: " + EnumChatFormatting.DARK_GREEN + currentGuiScale);
} else {
int scale = Math.min(10, MathHelper.parseIntWithDefault(args[1], 6));
Minecraft.getMinecraft().gameSettings.guiScale = scale;
- main.getUtils().sendMessage(EnumChatFormatting.GREEN + "\u2714 New GUI scale: " + EnumChatFormatting.DARK_GREEN + scale + EnumChatFormatting.GREEN + " (previous: " + EnumChatFormatting.DARK_GREEN + currentGuiScale + EnumChatFormatting.GREEN + ")");
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u2714 New GUI scale: " + EnumChatFormatting.DARK_GREEN + scale + EnumChatFormatting.GREEN + " (previous: " + EnumChatFormatting.DARK_GREEN + currentGuiScale + EnumChatFormatting.GREEN + ")");
}
- } else if (args[0].equalsIgnoreCase("update")) {
+ }
+ // sub-commands: update mod
+ else if (args[0].equalsIgnoreCase("update")) {
boolean updateCheckStarted = main.getVersionChecker().runUpdateCheck(true);
if (updateCheckStarted) {
- main.getUtils().sendMessage(EnumChatFormatting.GREEN + "\u279C Checking for a newer mod version...");
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "\u279C Checking for a newer mod version...");
// VersionChecker#handleVersionStatus will run with a 5 seconds delay
} else {
long nextUpdate = main.getVersionChecker().getNextCheck();
String waitingTime = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(nextUpdate),
TimeUnit.MILLISECONDS.toSeconds(nextUpdate) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(nextUpdate)));
- main.getUtils().sendMessage(new ChatComponentText("\u26A0 Update checker is on cooldown. Please wait " + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + waitingTime + EnumChatFormatting.RESET + EnumChatFormatting.RED + " more minutes before checking again.").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED)));
+ main.getChatHelper().sendMessage(EnumChatFormatting.RED, "\u26A0 Update checker is on cooldown. Please wait " + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + waitingTime + EnumChatFormatting.RESET + EnumChatFormatting.RED + " more minutes before checking again.");
}
} else if (args[0].equalsIgnoreCase("updateHelp")) {
- main.getUtils().sendMessage(new ChatComponentText("\u279C Update instructions:").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(true))
+ main.getChatHelper().sendMessage(new ChatComponentText("\u279C Update instructions:").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(true))
.appendSibling(new ChatComponentText("\n\u278A" + EnumChatFormatting.YELLOW + " download latest mod version").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false)
.setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, main.getVersionChecker().getDownloadUrl()))
.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Download the latest version of Cowmoonication\n\u279C Click to download latest mod file")))))
@@ -76,21 +89,53 @@ public class MooCommand extends CommandBase {
main.getVersionChecker().handleVersionStatus(true);
} else if (args[0].equalsIgnoreCase("folder")) {
try {
- Desktop.getDesktop().open(main.getUtils().getModsFolder());
+ Desktop.getDesktop().open(main.getModsFolder());
} catch (IOException e) {
- main.getUtils().sendMessage(new ChatComponentText("\u2716 An error occurred trying to open the mod's folder. I guess you have to open it manually \u00af\\_(\u30c4)_/\u00af").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED)));
+ main.getChatHelper().sendMessage(EnumChatFormatting.RED, "\u2716 An error occurred trying to open the mod's folder. I guess you have to open it manually \u00af\\_(\u30c4)_/\u00af");
e.printStackTrace();
}
+ }
+ // "catch-all" remaining sub-commands
+ else {
+ main.getChatHelper().sendMessage(new ChatComponentTranslation(getCommandUsage(sender)));
+ }
+ }
+
+ private void handleBestFriendAdd(String username) {
+ if (!Utils.isValidMcName(username)) {
+ main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username.");
+ return;
+ }
+
+ // TODO Add check if 'best friend' is on normal friend list
+ if (main.getFriends().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);
+ }
+ }
+
+ private void handleBestFriendRemove(String username) {
+ if (!Utils.isValidMcName(username)) {
+ main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username.");
+ return;
+ }
+
+ boolean removed = main.getFriends().removeBestFriend(username);
+ if (removed) {
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Removed " + EnumChatFormatting.DARK_GREEN + username + EnumChatFormatting.GREEN + " from best friends list.");
} else {
- main.getUtils().sendMessage(new ChatComponentTranslation(getCommandUsage(sender)));
+ main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " isn't a best friend.");
}
}
private void handleListBestFriends() {
Set<String> bestFriends = main.getFriends().getBestFriends();
- // TODO show fancy gui with list of best friends (maybe just the mod's settings?)
- main.getUtils().sendMessage(EnumChatFormatting.GREEN + "\u279C Best friends: " + EnumChatFormatting.DARK_GREEN + String.join(EnumChatFormatting.GREEN + ", " + EnumChatFormatting.DARK_GREEN, bestFriends));
+ // 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));
}
@Override
@@ -101,12 +146,14 @@ public class MooCommand extends CommandBase {
@Override
public String getCommandUsage(ICommandSender sender) {
IChatComponent usage = new ChatComponentText("\u279C Cowmoonication commands:").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(true))
- .appendSibling(createCmdHelpSection(1, "Login/Logout Notifications"))
- .appendSibling(createCmdHelpEntry("friends", "Add/remove best friends"))
+ .appendSibling(createCmdHelpSection(1, "Friends"))
+ .appendSibling(createCmdHelpEntry("add", "Add best friends"))
+ .appendSibling(createCmdHelpEntry("remove", "Remove best friends"))
.appendSibling(createCmdHelpEntry("list", "View list of best friends"))
+ .appendSibling(createCmdHelpEntry("nameChangeCheck", "Force a scan for changed names of best friends"))
.appendSibling(createCmdHelpEntry("toggle", "Toggle show/hide all join/leave notifications"))
.appendSibling(createCmdHelpSection(2, "Miscellaneous"))
- .appendSibling(createCmdHelpEntry("config", "Open configuration GUI"))
+ .appendSibling(createCmdHelpEntry("config", "Open mod's configuration"))
.appendSibling(createCmdHelpEntry("guiScale", "Change GUI scale"))
.appendSibling(createCmdHelpSection(3, "Update mod"))
.appendSibling(createCmdHelpEntry("update", "Check for new mod updates"))
@@ -139,7 +186,11 @@ public class MooCommand extends CommandBase {
@Override
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) {
if (args.length == 1) {
- return getListOfStringsMatchingLastWord(args, "config", "friends", "list", "toggle", "guiscale", "update", "updateHelp", "version", "folder", "help");
+ return getListOfStringsMatchingLastWord(args,
+ /* friends */ "add", "remove", "list", "nameChangeCheck", "toggle",
+ /* miscellaneous */ "guiscale", "config",
+ /* update mod */ "update", "updateHelp", "version", "folder",
+ /* help */ "help");
}
return null;
}
diff --git a/src/main/java/eu/olli/cowmoonication/config/MooConfig.java b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java
index e9b2146..d13758c 100644
--- a/src/main/java/eu/olli/cowmoonication/config/MooConfig.java
+++ b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java
@@ -1,7 +1,6 @@
package eu.olli.cowmoonication.config;
import eu.olli.cowmoonication.Cowmoonication;
-import eu.olli.cowmoonication.util.Utils;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
@@ -15,23 +14,19 @@ import java.util.List;
public class MooConfig {
public static boolean doUpdateCheck;
public static boolean filterFriendNotifications;
- private static String[] bestFriends;
private static Configuration cfg = null;
- private final Cowmoonication main;
- public MooConfig(Configuration configuration, Cowmoonication main) {
- this.main = main;
+ public MooConfig(Configuration configuration) {
cfg = configuration;
initConfig();
}
- public static Configuration getConfig() {
+ static Configuration getConfig() {
return cfg;
}
private void initConfig() {
syncFromFile();
- main.getFriends().syncFriends(bestFriends);
MinecraftForge.EVENT_BUS.register(new ConfigEventHandler());
}
@@ -76,32 +71,22 @@ public class MooConfig {
final boolean FILTER_FRIEND_NOTIFICATIONS = true;
Property propFilterFriendNotify = cfg.get(Configuration.CATEGORY_CLIENT, "filterFriendNotifications", FILTER_FRIEND_NOTIFICATIONS, "Set to false to receive all login/logout messages, set to true to only get notifications of 'best friends' joining/leaving");
- final String[] BEST_FRIENDS_DEFAULT_VALUE = new String[]{"Cow"};
- Property propBestFriends = cfg.get(Configuration.CATEGORY_CLIENT, "bestFriends", BEST_FRIENDS_DEFAULT_VALUE, "List of best friends: receive login/logout notifications from them");
- propBestFriends.setValidationPattern(Utils.VALID_USERNAME);
-
List<String> propOrderGeneral = new ArrayList<>();
propOrderGeneral.add(propDoUpdateCheck.getName());
propOrderGeneral.add(propFilterFriendNotify.getName());
- propOrderGeneral.add(propBestFriends.getName());
cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral);
if (readFieldsFromConfig) {
doUpdateCheck = propDoUpdateCheck.getBoolean(DO_UPDATE_CHECK);
filterFriendNotifications = propFilterFriendNotify.getBoolean(FILTER_FRIEND_NOTIFICATIONS);
- bestFriends = propBestFriends.getStringList();
}
propDoUpdateCheck.set(doUpdateCheck);
propFilterFriendNotify.set(filterFriendNotifications);
- propBestFriends.set(bestFriends);
if (cfg.hasChanged()) {
cfg.save();
}
- if (propBestFriends.hasChanged()) {
- main.getFriends().syncFriends(bestFriends);
- }
}
public void toggleNotifications() {
diff --git a/src/main/java/eu/olli/cowmoonication/friends/Friend.java b/src/main/java/eu/olli/cowmoonication/friends/Friend.java
new file mode 100644
index 0000000..889e087
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/friends/Friend.java
@@ -0,0 +1,78 @@
+package eu.olli.cowmoonication.friends;
+
+import com.google.gson.InstanceCreator;
+
+import java.lang.reflect.Type;
+import java.util.Objects;
+import java.util.UUID;
+
+public class Friend {
+ public static final Friend FRIEND_NOT_FOUND = new Friend(null, null, -1);
+ private UUID id;
+ private String name;
+ private long lastChecked;
+
+ /**
+ * No-args constructor for GSON
+ */
+ private Friend() {
+ this.lastChecked = System.currentTimeMillis();
+ }
+
+ private Friend(UUID uuid, String name, long lastChecked) {
+ this.id = uuid;
+ this.name = name;
+ if (lastChecked > 0) {
+ this.lastChecked = lastChecked;
+ }
+ }
+
+ 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);
+ }
+
+ public static class FriendCreator implements InstanceCreator {
+ @Override
+ public Friend createInstance(Type type) {
+ return new Friend();
+ }
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/friends/Friends.java b/src/main/java/eu/olli/cowmoonication/friends/Friends.java
new file mode 100644
index 0000000..e37b4a4
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/friends/Friends.java
@@ -0,0 +1,167 @@
+package eu.olli.cowmoonication.friends;
+
+import com.google.gson.Gson;
+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.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<Friend> 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<String> getBestFriends() {
+ return bestFriends.stream().map(Friend::getName).collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ 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 = dumpJson(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<Friend> parseJson(String bestFriendsData) {
+ Type collectionType = new TypeToken<Set<Friend>>() {
+ }.getType();
+ return new Gson().fromJson(bestFriendsData, collectionType);
+ }
+
+ private String dumpJson(Set<Friend> bestFriends) {
+ return new Gson().toJson(bestFriends);
+ }
+
+ private enum UpdateStatus {
+ IDLE, NAME_CHANGED, NO_NAME_CHANGES
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java
index 54f8025..42a45e0 100644
--- a/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java
+++ b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java
@@ -44,7 +44,7 @@ public class ChatListener {
if (nameEnd == -1) {
nameEnd = text.indexOf(" left");
}
- boolean isBestFriend = main.getFriends().isBestFriend(text.substring(0, nameEnd));
+ boolean isBestFriend = main.getFriends().isBestFriend(text.substring(0, nameEnd), false);
if (!isBestFriend) {
e.setCanceled(true);
}
@@ -58,10 +58,10 @@ public class ChatListener {
if (!Mouse.getEventButtonState() && Mouse.getEventButton() == 1 && Keyboard.isKeyDown(Keyboard.KEY_LMENU)) { // alt key pressed and right mouse button being released
IChatComponent chatComponent = Minecraft.getMinecraft().ingameGUI.getChatGUI().getChatComponent(Mouse.getX(), Mouse.getY());
if (chatComponent != null) {
- String chatData = main.getUtils().cleanChatComponent(chatComponent);
+ String chatData = main.getChatHelper().cleanChatComponent(chatComponent);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(chatData), null);
- main.getUtils().sendAboveChatMessage(EnumChatFormatting.YELLOW + "Copied chat component to clipboard:", "" + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276E" + EnumChatFormatting.RESET + chatComponent.getUnformattedText() + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276F");
+ main.getChatHelper().sendAboveChatMessage(EnumChatFormatting.YELLOW + "Copied chat component to clipboard:", "" + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276E" + EnumChatFormatting.RESET + chatComponent.getUnformattedText() + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276F");
}
}
}
@@ -76,7 +76,7 @@ public class ChatListener {
lastTypedChars += eventCharacter;
if (lastTypedChars.equalsIgnoreCase("/r ")) {
// replace /r with /msg <last user>
- main.getUtils().sendAboveChatMessage("Sending message to " + lastPMSender + "!");
+ main.getChatHelper().sendAboveChatMessage("Sending message to " + lastPMSender + "!");
Minecraft.getMinecraft().displayGuiScreen(new GuiChat("/w " + lastPMSender + " "));
}
} else if (Keyboard.getEventKey() == Keyboard.KEY_BACK) { // Backspace
@@ -106,7 +106,7 @@ public class ChatListener {
public void onRenderChatGui(RenderGameOverlayEvent.Chat e) {
if (e.type == RenderGameOverlayEvent.ElementType.CHAT) {
// render message above chat box
- String[] aboveChatMessage = main.getUtils().getAboveChatMessage();
+ String[] aboveChatMessage = main.getChatHelper().getAboveChatMessage();
if (aboveChatMessage != null) {
float chatHeightFocused = Minecraft.getMinecraft().gameSettings.chatHeightFocused;
float chatScale = Minecraft.getMinecraft().gameSettings.chatScale;
diff --git a/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java b/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java
index b817ed8..304806d 100644
--- a/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java
+++ b/src/main/java/eu/olli/cowmoonication/listener/PlayerListener.java
@@ -1,6 +1,7 @@
package eu.olli.cowmoonication.listener;
import eu.olli.cowmoonication.Cowmoonication;
+import eu.olli.cowmoonication.util.TickDelay;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
@@ -14,5 +15,11 @@ public class PlayerListener {
@SubscribeEvent
public void onServerJoin(FMLNetworkEvent.ClientConnectedToServerEvent e) {
main.getVersionChecker().runUpdateCheck(false);
+ new TickDelay(() -> main.getChatHelper().sendOfflineMessages(), 6 * 20);
+ }
+
+ @SubscribeEvent
+ public void onServerLeave(FMLNetworkEvent.ClientDisconnectionFromServerEvent e) {
+ main.getFriends().saveBestFriends();
}
}
diff --git a/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java
new file mode 100644
index 0000000..5ff7d5d
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java
@@ -0,0 +1,70 @@
+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.friends.Friend;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+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;
+
+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://api.mojang.com/user/profiles/%s/names";
+ private static ExecutorService pool = Executors.newCachedThreadPool();
+ private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(Friend.class, new Friend.FriendCreator()).create();
+
+ private ApiUtils() {
+ }
+
+ public static void fetchFriendData(String name, Consumer<Friend> action) {
+ pool.execute(() -> action.accept(getFriend(name)));
+ }
+
+ private static Friend getFriend(String name) {
+ try {
+ HttpURLConnection connection = (HttpURLConnection) new URL(NAME_TO_UUID_URL + name).openConnection();
+ connection.setReadTimeout(5000);
+ if (connection.getResponseCode() == 204) {
+ return Friend.FRIEND_NOT_FOUND;
+ } else if (connection.getResponseCode() == 200) {
+ return gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), Friend.class);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static void fetchCurrentName(Friend friend, Consumer<String> action) {
+ pool.execute(() -> action.accept(getCurrentName(friend)));
+ }
+
+ private static String getCurrentName(Friend friend) {
+ try {
+ HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_TO_NAME_URL, UUIDTypeAdapter.fromUUID(friend.getUuid()))).openConnection();
+ connection.setReadTimeout(5000);
+ if (connection.getResponseCode() == 204) {
+ return UUID_NOT_FOUND;
+ } else if (connection.getResponseCode() == 200) {
+ JsonArray nameHistoryData = new JsonParser().parse(new BufferedReader(new InputStreamReader(connection.getInputStream()))).getAsJsonArray();
+ if (nameHistoryData.size() > 0) {
+ return nameHistoryData.get(nameHistoryData.size() - 1).getAsJsonObject().get("name").getAsString();
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/util/ChatHelper.java b/src/main/java/eu/olli/cowmoonication/util/ChatHelper.java
new file mode 100644
index 0000000..a0820c0
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/util/ChatHelper.java
@@ -0,0 +1,74 @@
+package eu.olli.cowmoonication.util;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.ChatStyle;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IChatComponent;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.common.MinecraftForge;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ChatHelper {
+ private static final Pattern USELESS_JSON_CONTENT_PATTERN = Pattern.compile("\"[A-Za-z]+\":false,?");
+ private static final int DISPLAY_DURATION = 5000;
+ private List<IChatComponent> offlineMessages = new ArrayList<>();
+ private String[] aboveChatMessage;
+ private long aboveChatMessageExpiration;
+
+ public ChatHelper() {
+ }
+
+ public void sendMessage(EnumChatFormatting color, String text) {
+ sendMessage(new ChatComponentText(text).setChatStyle(new ChatStyle().setColor(color)));
+ }
+
+ public void sendMessage(IChatComponent chatComponent) {
+ ClientChatReceivedEvent event = new ClientChatReceivedEvent((byte) 1, chatComponent);
+ MinecraftForge.EVENT_BUS.post(event);
+ if (!event.isCanceled()) {
+ if (Minecraft.getMinecraft().thePlayer == null) {
+ offlineMessages.add(event.message);
+ } else {
+ Minecraft.getMinecraft().thePlayer.addChatMessage(event.message);
+ }
+ }
+ }
+
+ public void sendOfflineMessages() {
+ if (Minecraft.getMinecraft().thePlayer != null) {
+ Iterator<IChatComponent> offlineMessages = this.offlineMessages.iterator();
+ if (offlineMessages.hasNext()) {
+ Minecraft.getMinecraft().thePlayer.playSound("random.levelup", 0.4F, 0.8F);
+ }
+ while (offlineMessages.hasNext()) {
+ Minecraft.getMinecraft().thePlayer.addChatMessage(offlineMessages.next());
+ offlineMessages.remove();
+ }
+ }
+ }
+
+ public void sendAboveChatMessage(String... text) {
+ aboveChatMessage = text;
+ aboveChatMessageExpiration = Minecraft.getSystemTime() + DISPLAY_DURATION;
+ }
+
+ public String[] getAboveChatMessage() {
+ if (aboveChatMessageExpiration < Minecraft.getSystemTime()) {
+ // message expired
+ aboveChatMessage = null;
+ }
+ return aboveChatMessage;
+ }
+
+ public String cleanChatComponent(IChatComponent chatComponent) {
+ String component = IChatComponent.Serializer.componentToJson(chatComponent);
+ Matcher jsonMatcher = USELESS_JSON_CONTENT_PATTERN.matcher(component);
+ return jsonMatcher.replaceAll("");
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/util/Utils.java b/src/main/java/eu/olli/cowmoonication/util/Utils.java
index 60e2ea3..0b890f0 100644
--- a/src/main/java/eu/olli/cowmoonication/util/Utils.java
+++ b/src/main/java/eu/olli/cowmoonication/util/Utils.java
@@ -1,65 +1,14 @@
package eu.olli.cowmoonication.util;
-import eu.olli.cowmoonication.Cowmoonication;
-import net.minecraft.client.Minecraft;
-import net.minecraft.util.ChatComponentText;
-import net.minecraft.util.IChatComponent;
-import net.minecraftforge.client.event.ClientChatReceivedEvent;
-import net.minecraftforge.common.MinecraftForge;
-
-import java.io.File;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class Utils {
- public static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$");
- private static final Pattern USELESS_JSON_CONTENT_PATTERN = Pattern.compile("\"[A-Za-z]+\":false,?");
- private final Cowmoonication main;
- private final File modsDir;
- private String[] aboveChatMessage;
- private long aboveChatMessageExpiration;
-
- public Utils(Cowmoonication main, File sourceFile) {
- this.main = main;
- modsDir = sourceFile.getParentFile();
- }
-
- public void sendMessage(String text) {
- sendMessage(new ChatComponentText(text));
- }
-
- public void sendMessage(IChatComponent chatComponent) {
- ClientChatReceivedEvent event = new ClientChatReceivedEvent((byte) 1, chatComponent);
- MinecraftForge.EVENT_BUS.post(event);
- if (!event.isCanceled()) {
- Minecraft.getMinecraft().thePlayer.addChatMessage(event.message);
- }
- }
+public final class Utils {
+ private static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$");
- public void sendAboveChatMessage(String... text) {
- aboveChatMessage = text;
- aboveChatMessageExpiration = Minecraft.getSystemTime() + 5000;
+ private Utils() {
}
- public String[] getAboveChatMessage() {
- if (aboveChatMessageExpiration < Minecraft.getSystemTime()) {
- // message expired
- aboveChatMessage = null;
- }
- return aboveChatMessage;
- }
-
- public boolean isValidMcName(String username) {
+ public static boolean isValidMcName(String username) {
return VALID_USERNAME.matcher(username).matches();
}
-
- public String cleanChatComponent(IChatComponent chatComponent) {
- String component = IChatComponent.Serializer.componentToJson(chatComponent);
- Matcher jsonMatcher = USELESS_JSON_CONTENT_PATTERN.matcher(component);
- return jsonMatcher.replaceAll("");
- }
-
- public File getModsFolder() {
- return modsDir;
- }
}
diff --git a/src/main/java/eu/olli/cowmoonication/util/VersionChecker.java b/src/main/java/eu/olli/cowmoonication/util/VersionChecker.java
index 5c29ae5..fa04c38 100644
--- a/src/main/java/eu/olli/cowmoonication/util/VersionChecker.java
+++ b/src/main/java/eu/olli/cowmoonication/util/VersionChecker.java
@@ -120,7 +120,7 @@ public class VersionChecker {
}
if (statusMsg != null) {
- main.getUtils().sendMessage(statusMsg);
+ main.getChatHelper().sendMessage(statusMsg);
}
}