aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/eu/olli/cowmoonication/Cowmoonication.java11
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/MooCommand.java177
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/exception/ApiContactException.java7
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/exception/InvalidPlayerNameException.java10
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/exception/MooCommandException.java9
-rw-r--r--src/main/java/eu/olli/cowmoonication/command/exception/ThrowingConsumer.java25
-rw-r--r--src/main/java/eu/olli/cowmoonication/data/HySkyBlockStats.java302
-rw-r--r--src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java4
-rw-r--r--src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java8
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/ApiUtils.java27
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/MooChatComponent.java186
-rw-r--r--src/main/java/eu/olli/cowmoonication/util/Utils.java25
-rw-r--r--src/main/resources/assets/cowmoonication/lang/en_US.lang1
13 files changed, 750 insertions, 42 deletions
diff --git a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
index ae61781..2460058 100644
--- a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
+++ b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java
@@ -5,10 +5,10 @@ import eu.olli.cowmoonication.command.ShrugCommand;
import eu.olli.cowmoonication.command.TabCompletableCommand;
import eu.olli.cowmoonication.config.MooConfig;
import eu.olli.cowmoonication.handler.FriendsHandler;
+import eu.olli.cowmoonication.handler.PlayerCache;
import eu.olli.cowmoonication.listener.ChatListener;
import eu.olli.cowmoonication.listener.PlayerListener;
import eu.olli.cowmoonication.util.ChatHelper;
-import eu.olli.cowmoonication.handler.PlayerCache;
import eu.olli.cowmoonication.util.VersionChecker;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.common.MinecraftForge;
@@ -31,6 +31,7 @@ public class Cowmoonication {
public static final String VERSION = "@VERSION@";
public static final String MODNAME = "@MODNAME@";
public static final String GITURL = "@GITURL@";
+ private static Cowmoonication instance;
private File modsDir;
private MooConfig config;
private FriendsHandler friendsHandler;
@@ -41,6 +42,7 @@ public class Cowmoonication {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e) {
+ instance = this;
logger = e.getModLog();
File modDir = new File(e.getModConfigurationDirectory(), MODID + File.separatorChar);
@@ -99,4 +101,11 @@ public class Cowmoonication {
public Logger getLogger() {
return logger;
}
+
+ /**
+ * Get mod's instance; instead of this method use dependency injection where possible
+ */
+ public static Cowmoonication getInstance() {
+ return instance;
+ }
}
diff --git a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
index 8800ff2..4ad4e2c 100644
--- a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
+++ b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java
@@ -2,17 +2,20 @@ package eu.olli.cowmoonication.command;
import com.mojang.realmsclient.util.Pair;
import eu.olli.cowmoonication.Cowmoonication;
+import eu.olli.cowmoonication.command.exception.ApiContactException;
+import eu.olli.cowmoonication.command.exception.InvalidPlayerNameException;
+import eu.olli.cowmoonication.command.exception.MooCommandException;
import eu.olli.cowmoonication.config.MooConfig;
import eu.olli.cowmoonication.config.MooGuiConfig;
import eu.olli.cowmoonication.data.Friend;
-import eu.olli.cowmoonication.util.ApiUtils;
+import eu.olli.cowmoonication.data.HySkyBlockStats;
import eu.olli.cowmoonication.data.HyStalkingData;
+import eu.olli.cowmoonication.util.ApiUtils;
+import eu.olli.cowmoonication.util.MooChatComponent;
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;
-import net.minecraft.command.ICommandSender;
+import net.minecraft.command.*;
import net.minecraft.event.ClickEvent;
import net.minecraft.event.HoverEvent;
import net.minecraft.util.*;
@@ -20,6 +23,7 @@ import net.minecraft.util.*;
import java.awt.*;
import java.io.IOException;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -39,12 +43,20 @@ public class MooCommand extends CommandBase {
// sub commands: friends
if (args[0].equalsIgnoreCase("stalk")) {
if (args.length != 2) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Usage: /" + getCommandName() + " stalk <playerName>");
+ throw new WrongUsageException("/" + getCommandName() + " stalk <playerName>");
} else if (!Utils.isValidMcName(args[1])) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "\"" + args[1] + "\" is not a valid player name.");
+ throw new InvalidPlayerNameException(args[1]);
} else {
handleStalking(args[1]);
}
+ } else if (args[0].equalsIgnoreCase("stalksb") || args[0].equalsIgnoreCase("stalkskyblock") || args[0].equalsIgnoreCase("skyblockstalk")) {
+ if (args.length != 2) {
+ throw new WrongUsageException("/" + getCommandName() + " stalkskyblock <playerName>");
+ } else if (!Utils.isValidMcName(args[1])) {
+ throw new InvalidPlayerNameException(args[1]);
+ } else {
+ handleStalkingSkyBlock(args[1]);
+ }
} else if (args.length == 2 && args[0].equalsIgnoreCase("add")) {
handleBestFriendAdd(args[1]);
} else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) {
@@ -84,7 +96,7 @@ public class MooCommand extends CommandBase {
String waitingTime = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(nextUpdate),
TimeUnit.MILLISECONDS.toSeconds(nextUpdate) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(nextUpdate)));
- 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.");
+ throw new MooCommandException("\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.getChatHelper().sendMessage(new ChatComponentText("\u279C Update instructions:").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(true))
@@ -104,8 +116,8 @@ public class MooCommand extends CommandBase {
try {
Desktop.getDesktop().open(main.getModsFolder());
} catch (IOException e) {
- 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();
+ throw new MooCommandException("\u2716 An error occurred trying to open the mod's folder. I guess you have to open it manually \u00af\\_(\u30c4)_/\u00af");
}
}
// "catch-all" remaining sub-commands
@@ -114,7 +126,7 @@ public class MooCommand extends CommandBase {
}
}
- private void handleApiKey(String[] args) {
+ private void handleApiKey(String[] args) throws CommandException {
if (args.length == 1) {
String firstSentence;
EnumChatFormatting color;
@@ -136,15 +148,14 @@ public class MooCommand extends CommandBase {
main.getConfig().syncFromFields();
main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Updated API key!");
} else {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "That doesn't look like a valid API key...");
+ throw new SyntaxErrorException("That doesn't look like a valid API key...");
}
}
}
- private void handleStalking(String playerName) {
+ private void handleStalking(String playerName) throws CommandException {
if (!Utils.isValidUuid(MooConfig.moo)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "You haven't set your Hypixel API key yet. 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.");
- return;
+ throw new MooCommandException("You haven't set your Hypixel API key yet. 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.");
}
main.getChatHelper().sendMessage(EnumChatFormatting.GRAY, "Stalking " + EnumChatFormatting.WHITE + playerName + EnumChatFormatting.GRAY + ". This may take a few seconds.");
boolean isBestFriend = main.getFriendsHandler().isBestFriend(playerName, true);
@@ -156,9 +167,9 @@ public class MooCommand extends CommandBase {
// fetch player uuid
ApiUtils.fetchFriendData(playerName, stalkedPlayer -> {
if (stalkedPlayer == null) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Sorry, could contact Mojang's API and thus couldn't stalk " + EnumChatFormatting.DARK_RED + playerName);
+ throw new ApiContactException("Mojang", "couldn't stalk " + EnumChatFormatting.DARK_RED + playerName);
} else if (stalkedPlayer.equals(Friend.FRIEND_NOT_FOUND)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + ".");
+ throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + ".");
} else {
// ... then stalk the player
stalkPlayer(stalkedPlayer);
@@ -178,11 +189,16 @@ public class MooCommand extends CommandBase {
} else {
ApiUtils.fetchPlayerOfflineStatus(stalkedPlayer, slothStalking -> {
if (slothStalking == null) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Something went wrong contacting the Slothpixel API. Couldn't stalk " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + " but they appear to be offline currently.");
+ throw new ApiContactException("Slothpixel", "couldn't stalk " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + " but they appear to be offline currently.");
} else if (slothStalking.hasNeverJoinedHypixel()) {
main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, EnumChatFormatting.GOLD + stalkedPlayer.getName() + EnumChatFormatting.YELLOW + " has " + EnumChatFormatting.GOLD + "never " + EnumChatFormatting.YELLOW + "been on Hypixel (or might be nicked).");
} else if (slothStalking.isHidingOnlineStatus()) {
- main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, slothStalking.getPlayerNameFormatted() + EnumChatFormatting.YELLOW + " is hiding their online status.");
+ main.getChatHelper().sendMessage(new ChatComponentText(slothStalking.getPlayerNameFormatted()).appendSibling(new ChatComponentText(" is hiding their online status from the Hypixel API. You can see their online status with ").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW)))
+ .appendSibling(new ChatComponentText("/profile " + slothStalking.getPlayerName()).setChatStyle(new ChatStyle()
+ .setColor(EnumChatFormatting.GOLD)
+ .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/profile " + slothStalking.getPlayerName()))
+ .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + "/profile " + slothStalking.getPlayerName())))))
+ .appendSibling(new ChatComponentText(" while you're in a lobby (tooltip of the player head on the top left).").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW))));
} else if (slothStalking.hasNeverLoggedOut()) {
Pair<String, String> lastOnline = Utils.getLastOnlineWords(slothStalking.getLastLogin());
@@ -202,20 +218,118 @@ public class MooCommand extends CommandBase {
}
} else {
String cause = (hyStalking != null) ? hyStalking.getCause() : null;
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Something went wrong contacting the Hypixel API. Couldn't stalk " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + (cause != null ? " (Reason: " + EnumChatFormatting.DARK_RED + cause + EnumChatFormatting.RED + ")" : "") + ".");
+ throw new ApiContactException("Hypixel", "couldn't stalk " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + (cause != null ? " (Reason: " + EnumChatFormatting.DARK_RED + cause + EnumChatFormatting.RED + ")" : "") + ".");
}
});
}
- private void handleBestFriendAdd(String username) {
+ private void handleStalkingSkyBlock(String playerName) throws CommandException {
+ if (!Utils.isValidUuid(MooConfig.moo)) {
+ throw new MooCommandException("You haven't set your Hypixel API key yet. 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.");
+ }
+ 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);
+ }
+ });
+ }
+ }
+
+ private void stalkSkyBlockStats(Friend stalkedPlayer) {
+ ApiUtils.fetchSkyBlockStats(stalkedPlayer, hySBStalking -> {
+ if (hySBStalking != null && hySBStalking.isSuccess()) {
+ HySkyBlockStats.Profile activeProfile = hySBStalking.getActiveProfile(stalkedPlayer.getUuid());
+
+ if (activeProfile == null) {
+ throw new MooCommandException("Looks like " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + " hasn't played SkyBlock yet.");
+ }
+
+ String highestSkill = null;
+ int highestLevel = -1;
+
+ MooChatComponent skillLevels = new MooChatComponent("Skill levels:").gold();
+ HySkyBlockStats.Profile.Member member = activeProfile.getMember(stalkedPlayer.getUuid());
+ for (Map.Entry<HySkyBlockStats.SkillLevel, Double> entry : member.getSkills().entrySet()) {
+ String skill = Utils.fancyCase(entry.getKey().name());
+ int level = entry.getKey().getLevel(entry.getValue());
+ if (level > 0) {
+ skillLevels.appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent(skill, String.valueOf(level)));
+ }
+
+ if (level > highestLevel) {
+ highestSkill = skill;
+ highestLevel = level;
+ }
+ }
+
+ // output inspired by /profiles hover
+ String coinsBankAndPurse = (activeProfile.getCoinBank() >= 0) ? Utils.formatNumberWithAbbreviations(activeProfile.getCoinBank() + member.getCoinPurse()) : "API access disabled";
+ Pair<String, String> fancyFirstJoined = member.getFancyFirstJoined();
+
+ MooChatComponent wealthHover = new MooChatComponent("Accessible coins:").gold()
+ .appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent("Purse", Utils.formatNumberWithAbbreviations(member.getCoinPurse())))
+ .appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent("Bank", (activeProfile.getCoinBank() != -1) ? Utils.formatNumberWithAbbreviations(activeProfile.getCoinBank()) : "API access disabled"));
+ if (activeProfile.coopCount() > 0) {
+ wealthHover.appendFreshSibling(new ChatComponentText(" "));
+ wealthHover.appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent("Co-op members", String.valueOf(activeProfile.coopCount())));
+ wealthHover.appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent("Co-ops' purses sum", Utils.formatNumberWithAbbreviations(activeProfile.getCoopCoinPurses(stalkedPlayer.getUuid()))));
+ }
+
+ MooChatComponent sbStats = new MooChatComponent("SkyBlock stats of " + stalkedPlayer.getName() + " (" + activeProfile.getCuteName() + ")").gold().bold().setUrl("https://sky.lea.moe/stats/" + stalkedPlayer.getName() + "/" + activeProfile.getCuteName(), "Click to view SkyBlock stats on sky.lea.moe")
+ .appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Coins", coinsBankAndPurse).setHover(wealthHover));
+ if (highestSkill != null) {
+ if (highestLevel == 0) {
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Highest Skill", "All skills level 0"));
+ } else {
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Highest Skill", highestSkill + " " + highestLevel).setHover(skillLevels));
+ }
+ } else {
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Highest Skill", "API access disabled"));
+ }
+
+ Pair<Integer, Integer> uniqueMinionsData = activeProfile.getUniqueMinions();
+ String uniqueMinions = String.valueOf(uniqueMinionsData.first());
+ if (uniqueMinionsData.second() > activeProfile.coopCount()) {
+ // all players have their unique minions api access disabled
+ uniqueMinions = "API access disabled";
+ } else if (uniqueMinionsData.second() > 0) {
+ // at least one player has their unique minions api access disabled
+ uniqueMinions += " or more (" + uniqueMinionsData.second() + "/" + (activeProfile.coopCount() + 1) + " have their API access disabled)";
+ }
+
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Unique Minions", uniqueMinions));
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Fairy Souls", (member.getFairySoulsCollected() >= 0) ? String.valueOf(member.getFairySoulsCollected()) : "API access disabled"));
+ sbStats.appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Profile age", fancyFirstJoined.first()).setHover(new MooChatComponent.KeyValueTooltipComponent("Join date", fancyFirstJoined.second())));
+
+ main.getChatHelper().sendMessage(sbStats);
+ } else {
+ String cause = (hySBStalking != null) ? hySBStalking.getCause() : null;
+ throw new ApiContactException("Hypixel", "couldn't stalk " + EnumChatFormatting.DARK_RED + stalkedPlayer.getName() + EnumChatFormatting.RED + (cause != null ? " (Reason: " + EnumChatFormatting.DARK_RED + cause + EnumChatFormatting.RED + ")" : "") + ".");
+ }
+ });
+ }
+
+ private void handleBestFriendAdd(String username) throws CommandException {
if (!Utils.isValidMcName(username)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username.");
- return;
+ throw new InvalidPlayerNameException(username);
}
// TODO Add check if 'best friend' is on normal friend list
if (main.getFriendsHandler().isBestFriend(username, true)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " is a best friend already.");
+ throw new MooCommandException(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
@@ -223,17 +337,16 @@ public class MooCommand extends CommandBase {
}
}
- private void handleBestFriendRemove(String username) {
+ private void handleBestFriendRemove(String username) throws CommandException {
if (!Utils.isValidMcName(username)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username.");
- return;
+ throw new InvalidPlayerNameException(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 {
- main.getChatHelper().sendMessage(EnumChatFormatting.RED, EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " isn't a best friend.");
+ throw new MooCommandException(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " isn't a best friend.");
}
}
@@ -255,9 +368,10 @@ public class MooCommand extends CommandBase {
}
private void sendCommandUsage(ICommandSender sender) {
- IChatComponent usage = new ChatComponentText("\u279C " + Cowmoonication.MODNAME + " commands:").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(true))
+ IChatComponent usage = new MooChatComponent("\u279C " + Cowmoonication.MODNAME + " commands:").gold().bold()
.appendSibling(createCmdHelpSection(1, "Friends"))
.appendSibling(createCmdHelpEntry("stalk", "Get info of player's status"))
+ .appendSibling(createCmdHelpEntry("stalkskyblock", "Get info of player's SkyBlock stats"))
.appendSibling(createCmdHelpEntry("add", "Add best friends"))
.appendSibling(createCmdHelpEntry("remove", "Remove best friends"))
.appendSibling(createCmdHelpEntry("list", "View list of best friends"))
@@ -282,11 +396,8 @@ public class MooCommand extends CommandBase {
private IChatComponent createCmdHelpEntry(String cmd, String usage) {
String command = "/" + this.getCommandName() + " " + cmd;
- ChatStyle clickableMsg = new ChatStyle()
- .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + command)))
- .setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command));
- return new ChatComponentText("\n").appendSibling(new ChatComponentText(command).setChatStyle(clickableMsg.createDeepCopy().setColor(EnumChatFormatting.GOLD)))
- .appendSibling(new ChatComponentText(" \u27A1 " + usage).setChatStyle(clickableMsg.createDeepCopy().setColor(EnumChatFormatting.YELLOW)));
+
+ return new MooChatComponent("\n").reset().appendSibling(new MooChatComponent.KeyValueChatComponent(command, usage, " \u27A1 ").setSuggestCommand(command));
}
@Override
@@ -298,7 +409,7 @@ public class MooCommand extends CommandBase {
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args,
- /* friends */ "stalk", "add", "remove", "list", "nameChangeCheck", "toggle",
+ /* friends */ "stalk", "stalkskyblock", "skyblockstalk", "add", "remove", "list", "nameChangeCheck", "toggle",
/* miscellaneous */ "config", "guiscale", "shrug", "apikey",
/* update mod */ "update", "updateHelp", "version", "folder",
/* help */ "help");
diff --git a/src/main/java/eu/olli/cowmoonication/command/exception/ApiContactException.java b/src/main/java/eu/olli/cowmoonication/command/exception/ApiContactException.java
new file mode 100644
index 0000000..9dd8d9e
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/command/exception/ApiContactException.java
@@ -0,0 +1,7 @@
+package eu.olli.cowmoonication.command.exception;
+
+public class ApiContactException extends MooCommandException {
+ public ApiContactException(String api, String failedAction) {
+ super("Sorry, couldn't contact the " + api + " API and thus " + failedAction);
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/command/exception/InvalidPlayerNameException.java b/src/main/java/eu/olli/cowmoonication/command/exception/InvalidPlayerNameException.java
new file mode 100644
index 0000000..7afdeaa
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/command/exception/InvalidPlayerNameException.java
@@ -0,0 +1,10 @@
+package eu.olli.cowmoonication.command.exception;
+
+import net.minecraft.command.SyntaxErrorException;
+import net.minecraft.util.EnumChatFormatting;
+
+public class InvalidPlayerNameException extends SyntaxErrorException {
+ public InvalidPlayerNameException(String playerName) {
+ super(EnumChatFormatting.DARK_RED + playerName + EnumChatFormatting.RED + "? This... doesn't look like a valid username.");
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/command/exception/MooCommandException.java b/src/main/java/eu/olli/cowmoonication/command/exception/MooCommandException.java
new file mode 100644
index 0000000..6c3cb08
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/command/exception/MooCommandException.java
@@ -0,0 +1,9 @@
+package eu.olli.cowmoonication.command.exception;
+
+import net.minecraft.command.CommandException;
+
+public class MooCommandException extends CommandException {
+ public MooCommandException(String msg) {
+ super("cowmoonication.commands.generic.exception", msg);
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/command/exception/ThrowingConsumer.java b/src/main/java/eu/olli/cowmoonication/command/exception/ThrowingConsumer.java
new file mode 100644
index 0000000..589ada6
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/command/exception/ThrowingConsumer.java
@@ -0,0 +1,25 @@
+package eu.olli.cowmoonication.command.exception;
+
+import eu.olli.cowmoonication.Cowmoonication;
+import net.minecraft.command.CommandException;
+import net.minecraft.util.ChatComponentTranslation;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IChatComponent;
+
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface ThrowingConsumer<T> extends Consumer<T> {
+ @Override
+ default void accept(T t) {
+ try {
+ acceptThrows(t);
+ } catch (CommandException e) {
+ IChatComponent errorMsg = new ChatComponentTranslation(e.getMessage(), e.getErrorObjects());
+ errorMsg.getChatStyle().setColor(EnumChatFormatting.RED);
+ Cowmoonication.getInstance().getChatHelper().sendMessage(errorMsg);
+ }
+ }
+
+ void acceptThrows(T t) throws CommandException;
+}
diff --git a/src/main/java/eu/olli/cowmoonication/data/HySkyBlockStats.java b/src/main/java/eu/olli/cowmoonication/data/HySkyBlockStats.java
new file mode 100644
index 0000000..a9735c3
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/data/HySkyBlockStats.java
@@ -0,0 +1,302 @@
+package eu.olli.cowmoonication.data;
+
+import com.mojang.realmsclient.util.Pair;
+import com.mojang.util.UUIDTypeAdapter;
+import eu.olli.cowmoonication.util.Utils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+public class HySkyBlockStats {
+ private boolean success;
+ private String cause;
+ private List<Profile> profiles;
+
+ /**
+ * No-args constructor for GSON
+ */
+ private HySkyBlockStats() {
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public Profile getActiveProfile(UUID uuid) {
+ if (profiles == null) {
+ return null;
+ }
+ Profile lastSavedProfile = null;
+ long latestSave = -1;
+ for (Profile profile : profiles) {
+ long lastProfileSave = profile.getMember(uuid).last_save;
+ if (latestSave < lastProfileSave) {
+ lastSavedProfile = profile;
+ latestSave = lastProfileSave;
+ }
+ }
+ return lastSavedProfile;
+ }
+
+ public static class Profile {
+ private String cute_name;
+ private Map<String, Member> members;
+ private Banking banking;
+
+ /**
+ * No-args constructor for GSON
+ */
+ private Profile() {
+ }
+
+ public String getCuteName() {
+ return cute_name;
+ }
+
+ public Member getMember(UUID uuid) {
+ return members.get(UUIDTypeAdapter.fromUUID(uuid));
+ }
+
+ public double getCoinBank() {
+ return (banking != null) ? banking.balance : -1;
+ }
+
+ public int coopCount() {
+ return members.size() - 1;
+ }
+
+ public double getCoopCoinPurses(UUID stalkedUuid) {
+ double coopCoinPurses = 0;
+ for (Map.Entry<String, Member> memberEntry : members.entrySet()) {
+ if (memberEntry.getKey().equals(UUIDTypeAdapter.fromUUID(stalkedUuid))) {
+ // don't include stalked player's purse again, only coops' purse
+ continue;
+ }
+ coopCoinPurses += memberEntry.getValue().getCoinPurse();
+ }
+ return coopCoinPurses;
+ }
+
+ public Pair<Integer, Integer> getUniqueMinions() {
+ int uniqueMinions = 0;
+ int membersWithDisabledApi = 0;
+ for (Member member : members.values()) {
+ if (member.crafted_generators != null) {
+ if (uniqueMinions > 0) {
+ --uniqueMinions; // subtract duplicate COBBLESTONE_1 minion
+ }
+ uniqueMinions += member.crafted_generators.size();
+ } else {
+ ++membersWithDisabledApi;
+ }
+ }
+ return Pair.of(uniqueMinions, membersWithDisabledApi);
+ }
+
+ public static class Member {
+ private long last_save;
+ private long first_join;
+ private double coin_purse;
+ private List<String> crafted_generators;
+ private int fairy_souls_collected = -1;
+ private double experience_skill_farming = -1;
+ private double experience_skill_mining = -1;
+ private double experience_skill_combat = -1;
+ private double experience_skill_foraging = -1;
+ private double experience_skill_fishing = -1;
+ private double experience_skill_enchanting = -1;
+ private double experience_skill_alchemy = -1;
+ private double experience_skill_carpentry = -1;
+ private double experience_skill_runecrafting = -1;
+ private double experience_skill_taming = -1;
+
+ /**
+ * No-args constructor for GSON
+ */
+ private Member() {
+ }
+
+ public Pair<String, String> getFancyFirstJoined() {
+ return Utils.getLastOnlineWords(first_join);
+ }
+
+ public double getCoinPurse() {
+ return coin_purse;
+ }
+
+ public int getFairySoulsCollected() {
+ return fairy_souls_collected;
+ }
+
+ public Map<SkillLevel, Double> getSkills() {
+ Map<SkillLevel, Double> skills = new TreeMap<>();
+ if (experience_skill_farming >= 0) {
+ skills.put(SkillLevel.FARMING, experience_skill_farming);
+ }
+ if (experience_skill_mining >= 0) {
+ skills.put(SkillLevel.MINING, experience_skill_mining);
+ }
+ if (experience_skill_combat >= 0) {
+ skills.put(SkillLevel.COMBAT, experience_skill_combat);
+ }
+ if (experience_skill_foraging >= 0) {
+ skills.put(SkillLevel.FORAGING, experience_skill_foraging);
+ }
+ if (experience_skill_fishing >= 0) {
+ skills.put(SkillLevel.FISHING, experience_skill_fishing);
+ }
+ if (experience_skill_enchanting >= 0) {
+ skills.put(SkillLevel.ENCHANTING, experience_skill_enchanting);
+ }
+ if (experience_skill_alchemy >= 0) {
+ skills.put(SkillLevel.ALCHEMY, experience_skill_alchemy);
+ }
+ if (experience_skill_carpentry >= 0) {
+ skills.put(SkillLevel.CARPENTRY, experience_skill_carpentry);
+ }
+ if (experience_skill_runecrafting >= 0) {
+ skills.put(SkillLevel.RUNECRAFTING, experience_skill_runecrafting);
+ }
+ if (experience_skill_taming >= 0) {
+ skills.put(SkillLevel.TAMING, experience_skill_taming);
+ }
+ return skills;
+ }
+ }
+
+ public static class Banking {
+ private double balance;
+ // private List<Transaction> transactions;
+
+ /**
+ * No-args constructor for GSON
+ */
+ private Banking() {
+ }
+
+ // private class Transaction {
+ // private int amount;
+ // private long timestamp;
+ // private Transaction.Action action;
+ // private String initiator_name;
+ //
+ // /**
+ // * No-args constructor for GSON
+ // */
+ // private Transaction() {
+ // }
+ // }
+ }
+ }
+
+ public enum SkillLevel {
+ FARMING, MINING, COMBAT, FORAGING, FISHING, ENCHANTING, ALCHEMY, CARPENTRY, RUNECRAFTING(true), TAMING;
+ private final boolean alternativeXpFormula;
+ private static final TreeMap<Integer, Integer> XP_TO_LEVEL = new TreeMap<>();
+ private static final TreeMap<Integer, Integer> XP_TO_LEVEL_ALTERNATIVE = new TreeMap<>();
+
+ static {
+ // exp data taken from https://api.hypixel.net/resources/skyblock/skills
+ XP_TO_LEVEL.put(0, 0);
+ XP_TO_LEVEL.put(50, 1);
+ XP_TO_LEVEL.put(175, 2);
+ XP_TO_LEVEL.put(375, 3);
+ XP_TO_LEVEL.put(675, 4);
+ XP_TO_LEVEL.put(1175, 5);
+ XP_TO_LEVEL.put(1925, 6);
+ XP_TO_LEVEL.put(2925, 7);
+ XP_TO_LEVEL.put(4425, 8);
+ XP_TO_LEVEL.put(6425, 9);
+ XP_TO_LEVEL.put(9925, 10);
+ XP_TO_LEVEL.put(14925, 11);
+ XP_TO_LEVEL.put(22425, 12);
+ XP_TO_LEVEL.put(32425, 13);
+ XP_TO_LEVEL.put(47425, 14);
+ XP_TO_LEVEL.put(67425, 15);
+ XP_TO_LEVEL.put(97425, 16);
+ XP_TO_LEVEL.put(147425, 17);
+ XP_TO_LEVEL.put(222425, 18);
+ XP_TO_LEVEL.put(322425, 19);
+ XP_TO_LEVEL.put(522425, 20);
+ XP_TO_LEVEL.put(822425, 21);
+ XP_TO_LEVEL.put(1222425, 22);
+ XP_TO_LEVEL.put(1722425, 23);
+ XP_TO_LEVEL.put(2322425, 24);
+ XP_TO_LEVEL.put(3022425, 25);
+ XP_TO_LEVEL.put(3822425, 26);
+ XP_TO_LEVEL.put(4722425, 27);
+ XP_TO_LEVEL.put(5722425, 28);
+ XP_TO_LEVEL.put(6822425, 29);
+ XP_TO_LEVEL.put(8022425, 30);
+ XP_TO_LEVEL.put(9322425, 31);
+ XP_TO_LEVEL.put(10722425, 32);
+ XP_TO_LEVEL.put(12222425, 33);
+ XP_TO_LEVEL.put(13822425, 34);
+ XP_TO_LEVEL.put(15522425, 35);
+ XP_TO_LEVEL.put(17322425, 36);
+ XP_TO_LEVEL.put(19222425, 37);
+ XP_TO_LEVEL.put(21222425, 38);
+ XP_TO_LEVEL.put(23322425, 39);
+ XP_TO_LEVEL.put(25522425, 40);
+ XP_TO_LEVEL.put(27822425, 41);
+ XP_TO_LEVEL.put(30222425, 42);
+ XP_TO_LEVEL.put(32722425, 43);
+ XP_TO_LEVEL.put(35322425, 44);
+ XP_TO_LEVEL.put(38072425, 45);
+ XP_TO_LEVEL.put(40972425, 46);
+ XP_TO_LEVEL.put(44072425, 47);
+ XP_TO_LEVEL.put(47472425, 48);
+ XP_TO_LEVEL.put(51172425, 49);
+ XP_TO_LEVEL.put(55172425, 50);
+
+ XP_TO_LEVEL_ALTERNATIVE.put(0, 0);
+ XP_TO_LEVEL_ALTERNATIVE.put(50, 1);
+ XP_TO_LEVEL_ALTERNATIVE.put(150, 2);
+ XP_TO_LEVEL_ALTERNATIVE.put(275, 3);
+ XP_TO_LEVEL_ALTERNATIVE.put(435, 4);
+ XP_TO_LEVEL_ALTERNATIVE.put(635, 5);
+ XP_TO_LEVEL_ALTERNATIVE.put(885, 6);
+ XP_TO_LEVEL_ALTERNATIVE.put(1200, 7);
+ XP_TO_LEVEL_ALTERNATIVE.put(1600, 8);
+ XP_TO_LEVEL_ALTERNATIVE.put(2100, 9);
+ XP_TO_LEVEL_ALTERNATIVE.put(2725, 10);
+ XP_TO_LEVEL_ALTERNATIVE.put(3510, 11);
+ XP_TO_LEVEL_ALTERNATIVE.put(4510, 12);
+ XP_TO_LEVEL_ALTERNATIVE.put(5760, 13);
+ XP_TO_LEVEL_ALTERNATIVE.put(7325, 14);
+ XP_TO_LEVEL_ALTERNATIVE.put(9325, 15);
+ XP_TO_LEVEL_ALTERNATIVE.put(11825, 16);
+ XP_TO_LEVEL_ALTERNATIVE.put(14950, 17);
+ XP_TO_LEVEL_ALTERNATIVE.put(18950, 18);
+ XP_TO_LEVEL_ALTERNATIVE.put(23950, 19);
+ XP_TO_LEVEL_ALTERNATIVE.put(30200, 20);
+ XP_TO_LEVEL_ALTERNATIVE.put(38050, 21);
+ XP_TO_LEVEL_ALTERNATIVE.put(47850, 22);
+ XP_TO_LEVEL_ALTERNATIVE.put(60100, 23);
+ XP_TO_LEVEL_ALTERNATIVE.put(75400, 24);
+
+ }
+
+ SkillLevel() {
+ this(false);
+ }
+
+ SkillLevel(boolean alternativeXpFormula) {
+ this.alternativeXpFormula = alternativeXpFormula;
+ }
+
+ public int getLevel(double exp) {
+ if (alternativeXpFormula) {
+ return XP_TO_LEVEL_ALTERNATIVE.floorEntry((int) exp).getValue();
+ } else {
+ return XP_TO_LEVEL.floorEntry((int) exp).getValue();
+ }
+ }
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java b/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java
index 1072634..6fc7639 100644
--- a/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java
+++ b/src/main/java/eu/olli/cowmoonication/data/SlothStalkingData.java
@@ -15,6 +15,10 @@ public class SlothStalkingData {
public SlothStalkingData() {
}
+ public String getPlayerName() {
+ return username;
+ }
+
public String getPlayerNameFormatted() {
return rank_formatted.replace('&', 'ยง') + " " + username;
}
diff --git a/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java
index 119e289..9ec469e 100644
--- a/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java
+++ b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java
@@ -3,11 +3,13 @@ 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.command.exception.ApiContactException;
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.command.PlayerNotFoundException;
import net.minecraft.event.ClickEvent;
import net.minecraft.event.HoverEvent;
import net.minecraft.util.ChatComponentText;
@@ -56,9 +58,9 @@ public class FriendsHandler {
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.");
+ throw new ApiContactException("Mojang", "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 + ".");
+ throw new PlayerNotFoundException("There is no player with the name " + EnumChatFormatting.DARK_RED + name + EnumChatFormatting.RED + ".");
} else {
boolean added = bestFriends.add(friend);
if (added) {
@@ -108,7 +110,7 @@ public class FriendsHandler {
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?");
+ 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());
diff --git a/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java
index aca1819..8515593 100644
--- a/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java
+++ b/src/main/java/eu/olli/cowmoonication/util/ApiUtils.java
@@ -4,8 +4,10 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.mojang.util.UUIDTypeAdapter;
import eu.olli.cowmoonication.Cowmoonication;
+import eu.olli.cowmoonication.command.exception.ThrowingConsumer;
import eu.olli.cowmoonication.config.MooConfig;
import eu.olli.cowmoonication.data.Friend;
+import eu.olli.cowmoonication.data.HySkyBlockStats;
import eu.olli.cowmoonication.data.HyStalkingData;
import eu.olli.cowmoonication.data.SlothStalkingData;
import org.apache.http.HttpStatus;
@@ -18,20 +20,20 @@ import java.net.HttpURLConnection;
import java.net.URL;
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 final String STALKING_URL_OFFICIAL = "https://api.hypixel.net/status?key=%s&uuid=%s";
+ private static final String SKYBLOCK_STATS_URL_OFFICIAL = "https://api.hypixel.net/skyblock/profiles?key=%s&uuid=%s";
private static final String STALKING_URL_UNOFFICIAL = "https://api.slothpixel.me/api/players/%s";
private static ExecutorService pool = Executors.newCachedThreadPool();
private ApiUtils() {
}
- public static void fetchFriendData(String name, Consumer<Friend> action) {
+ public static void fetchFriendData(String name, ThrowingConsumer<Friend> action) {
pool.execute(() -> action.accept(getFriend(name)));
}
@@ -48,7 +50,7 @@ public class ApiUtils {
return null;
}
- public static void fetchCurrentName(Friend friend, Consumer<String> action) {
+ public static void fetchCurrentName(Friend friend, ThrowingConsumer<String> action) {
pool.execute(() -> action.accept(getCurrentName(friend)));
}
@@ -68,7 +70,7 @@ public class ApiUtils {
return null;
}
- public static void fetchPlayerStatus(Friend friend, Consumer<HyStalkingData> action) {
+ public static void fetchPlayerStatus(Friend friend, ThrowingConsumer<HyStalkingData> action) {
pool.execute(() -> action.accept(stalkPlayer(friend)));
}
@@ -83,7 +85,22 @@ public class ApiUtils {
return null;
}
- public static void fetchPlayerOfflineStatus(Friend stalkedPlayer, Consumer<SlothStalkingData> action) {
+ public static void fetchSkyBlockStats(Friend friend, ThrowingConsumer<HySkyBlockStats> action) {
+ pool.execute(() -> action.accept(stalkSkyBlockStats(friend)));
+ }
+
+ private static HySkyBlockStats stalkSkyBlockStats(Friend friend) {
+ try (BufferedReader reader = makeApiCall(String.format(SKYBLOCK_STATS_URL_OFFICIAL, MooConfig.moo, UUIDTypeAdapter.fromUUID(friend.getUuid())))) {
+ if (reader != null) {
+ return GsonUtils.fromJson(reader, HySkyBlockStats.class);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static void fetchPlayerOfflineStatus(Friend stalkedPlayer, ThrowingConsumer<SlothStalkingData> action) {
pool.execute(() -> action.accept(stalkOfflinePlayer(stalkedPlayer)));
}
diff --git a/src/main/java/eu/olli/cowmoonication/util/MooChatComponent.java b/src/main/java/eu/olli/cowmoonication/util/MooChatComponent.java
new file mode 100644
index 0000000..a489391
--- /dev/null
+++ b/src/main/java/eu/olli/cowmoonication/util/MooChatComponent.java
@@ -0,0 +1,186 @@
+package eu.olli.cowmoonication.util;
+
+import net.minecraft.event.ClickEvent;
+import net.minecraft.event.HoverEvent;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IChatComponent;
+
+public class MooChatComponent extends ChatComponentText {
+ public MooChatComponent(String msg) {
+ super(msg);
+ }
+
+ public MooChatComponent black() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.BLACK));
+ return this;
+ }
+
+ public MooChatComponent darkBlue() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_BLUE));
+ return this;
+ }
+
+ public MooChatComponent darkGreen() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_GREEN));
+ return this;
+ }
+
+ public MooChatComponent darkAqua() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_AQUA));
+ return this;
+ }
+
+ public MooChatComponent darkRed() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_RED));
+ return this;
+ }
+
+ public MooChatComponent darkPurple() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_PURPLE));
+ return this;
+ }
+
+ public MooChatComponent gold() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.GOLD));
+ return this;
+ }
+
+ public MooChatComponent gray() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.GRAY));
+ return this;
+ }
+
+ public MooChatComponent darkGray() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.DARK_GRAY));
+ return this;
+ }
+
+ public MooChatComponent blue() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.BLUE));
+ return this;
+ }
+
+ public MooChatComponent green() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.GREEN));
+ return this;
+ }
+
+ public MooChatComponent aqua() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.AQUA));
+ return this;
+ }
+
+ public MooChatComponent red() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.RED));
+ return this;
+ }
+
+ public MooChatComponent lightPurple() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.LIGHT_PURPLE));
+ return this;
+ }
+
+ public MooChatComponent yellow() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.YELLOW));
+ return this;
+ }
+
+ public MooChatComponent white() {
+ setChatStyle(getChatStyle().setColor(EnumChatFormatting.WHITE));
+ return this;
+ }
+
+ public MooChatComponent obfuscated() {
+ setChatStyle(getChatStyle().setObfuscated(true));
+ return this;
+ }
+
+ public MooChatComponent bold() {
+ setChatStyle(getChatStyle().setBold(true));
+ return this;
+ }
+
+ public MooChatComponent strikethrough() {
+ setChatStyle(getChatStyle().setStrikethrough(true));
+ return this;
+ }
+
+ public MooChatComponent underline() {
+ setChatStyle(getChatStyle().setUnderlined(true));
+ return this;
+ }
+
+ public MooChatComponent italic() {
+ setChatStyle(getChatStyle().setItalic(true));
+ return this;
+ }
+
+ public MooChatComponent reset() {
+ setChatStyle(getChatStyle().setParentStyle(null).setBold(false).setItalic(false).setObfuscated(false).setUnderlined(false).setStrikethrough(false));
+ return this;
+ }
+
+ public MooChatComponent setHover(IChatComponent hover) {
+ setChatStyle(getChatStyle().setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hover)));
+ return this;
+ }
+
+ public MooChatComponent setUrl(String url) {
+ setUrl(url, new KeyValueTooltipComponent("Click to visit", url));
+ return this;
+ }
+
+ public MooChatComponent setUrl(String url, String hover) {
+ setUrl(url, new MooChatComponent(hover).yellow());
+ return this;
+ }
+
+ public MooChatComponent setUrl(String url, IChatComponent hover) {
+ setChatStyle(getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)));
+ setHover(hover);
+ return this;
+ }
+
+ public MooChatComponent setSuggestCommand(String command) {
+ setChatStyle(getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command)));
+ setHover(new KeyValueChatComponent("Run", command, " "));
+ return this;
+ }
+
+ /**
+ * Appends the given component in a new line, without inheriting formatting of previous siblings.
+ *
+ * @see ChatComponentText#appendSibling appendSibling
+ */
+ public MooChatComponent appendFreshSibling(IChatComponent sibling) {
+ this.siblings.add(new ChatComponentText("\n").appendSibling(sibling));
+ return this;
+ }
+
+ @Deprecated
+ public MooChatComponent appendKeyValue(String key, String value) {
+ appendSibling(new MooChatComponent("\n").appendFreshSibling(new KeyValueChatComponent(key, value)));
+ return this;
+ }
+
+ public static class KeyValueChatComponent extends MooChatComponent {
+ public KeyValueChatComponent(String key, String value) {
+ this(key, value, ": ");
+ }
+
+ public KeyValueChatComponent(String key, String value, String separator) {
+ super(key);
+ appendText(separator);
+ gold().appendSibling(new MooChatComponent(value).yellow());
+ }
+ }
+
+ public static class KeyValueTooltipComponent extends MooChatComponent {
+ public KeyValueTooltipComponent(String key, String value) {
+ super(key);
+ appendText(": ");
+ gray().appendSibling(new MooChatComponent(value).yellow());
+ }
+ }
+}
diff --git a/src/main/java/eu/olli/cowmoonication/util/Utils.java b/src/main/java/eu/olli/cowmoonication/util/Utils.java
index 0ff4010..ec355b9 100644
--- a/src/main/java/eu/olli/cowmoonication/util/Utils.java
+++ b/src/main/java/eu/olli/cowmoonication/util/Utils.java
@@ -11,6 +11,7 @@ import java.util.regex.Pattern;
public final class Utils {
public static final Pattern VALID_UUID_PATTERN = Pattern.compile("^(\\w{8})-(\\w{4})-(\\w{4})-(\\w{4})-(\\w{12})$");
private static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$");
+ private static final char[] LARGE_NUMBERS = new char[]{'k', 'm', 'b', 't'};
private Utils() {
}
@@ -52,4 +53,28 @@ public final class Utils {
dateFormatted);
}
}
+
+ /**
+ * Formats a large number with abbreviations for each factor of a thousand (k, m, ...)
+ *
+ * @param number the number to format
+ * @return a String representing the number n formatted in a cool looking way.
+ * @see <a href="https://stackoverflow.com/a/4753866">Source</a>
+ */
+ public static String formatNumberWithAbbreviations(double number) {
+ return formatNumberWithAbbreviations(number, 0);
+ }
+
+ private static String formatNumberWithAbbreviations(double number, int iteration) {
+ @SuppressWarnings("IntegerDivisionInFloatingPointContext") double d = ((long) number / 100) / 10.0;
+ boolean isRound = (d * 10) % 10 == 0; //true if the decimal part is equal to 0 (then it's trimmed anyway)
+ // this determines the class, i.e. 'k', 'm' etc
+ // this decides whether to trim the decimals
+ // (int) d * 10 / 10 drops the decimal
+ return d < 1000 ? // this determines the class, i.e. 'k', 'm' etc
+ (d > 99.9 || isRound || d > 9.99 ? // this decides whether to trim the decimals
+ (int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal
+ ) + "" + LARGE_NUMBERS[iteration]
+ : formatNumberWithAbbreviations(d, iteration + 1);
+ }
}
diff --git a/src/main/resources/assets/cowmoonication/lang/en_US.lang b/src/main/resources/assets/cowmoonication/lang/en_US.lang
index 05cfc43..f66517c 100644
--- a/src/main/resources/assets/cowmoonication/lang/en_US.lang
+++ b/src/main/resources/assets/cowmoonication/lang/en_US.lang
@@ -8,3 +8,4 @@ cowmoonication.config.showGuildNotifications=Show guild notifications
cowmoonication.config.showGuildNotifications.tooltip=Set to true to receive guild members' login/logout messages, set to false hide them.
cowmoonication.config.tabCompletableNamesCommands=Commands with Tab-completable usernames
cowmoonication.config.tabCompletableNamesCommands.tooltip=List of commands with a username argument that should be Tab-completable.\nRequires game restart to take effect!
+cowmoonication.commands.generic.exception=%s \ No newline at end of file