diff options
author | Cow <cow@volloeko.de> | 2020-07-05 05:42:45 +0200 |
---|---|---|
committer | Cow <cow@volloeko.de> | 2020-07-05 05:42:45 +0200 |
commit | 1b446698398c648b38311975a6cfd54859ea5cfe (patch) | |
tree | 521ecc4ce9ad968281094eb8c5453dca606931e3 /src/main/java/eu/olli/cowlection/util | |
parent | edaca1fd41a612c71c526ceb20b89c5dec2d81b3 (diff) | |
download | Cowlection-1b446698398c648b38311975a6cfd54859ea5cfe.tar.gz Cowlection-1b446698398c648b38311975a6cfd54859ea5cfe.tar.bz2 Cowlection-1b446698398c648b38311975a6cfd54859ea5cfe.zip |
Renamed mod to Cowlection
Bumped version to 1.8.9-0.7.0
Diffstat (limited to 'src/main/java/eu/olli/cowlection/util')
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/ApiUtils.java | 138 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/ChatHelper.java | 82 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/GsonUtils.java | 28 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/ImageUtils.java | 76 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/MooChatComponent.java | 186 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/TickDelay.java | 29 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/Utils.java | 250 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowlection/util/VersionChecker.java | 140 |
8 files changed, 929 insertions, 0 deletions
diff --git a/src/main/java/eu/olli/cowlection/util/ApiUtils.java b/src/main/java/eu/olli/cowlection/util/ApiUtils.java new file mode 100644 index 0000000..b0a4cce --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/ApiUtils.java @@ -0,0 +1,138 @@ +package eu.olli.cowlection.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import com.mojang.util.UUIDTypeAdapter; +import eu.olli.cowlection.Cowlection; +import eu.olli.cowlection.command.exception.ThrowingConsumer; +import eu.olli.cowlection.config.MooConfig; +import eu.olli.cowlection.data.Friend; +import eu.olli.cowlection.data.HySkyBlockStats; +import eu.olli.cowlection.data.HyStalkingData; +import eu.olli.cowlection.data.SlothStalkingData; +import org.apache.http.HttpStatus; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ApiUtils { + public static final String UUID_NOT_FOUND = "UUID-NOT-FOUND"; + private static final String NAME_TO_UUID_URL = "https://api.mojang.com/users/profiles/minecraft/"; + private static final String UUID_TO_NAME_URL = "https://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 final ExecutorService pool = Executors.newCachedThreadPool(); + + private ApiUtils() { + } + + public static void fetchFriendData(String name, ThrowingConsumer<Friend> action) { + pool.execute(() -> action.accept(getFriend(name))); + } + + private static Friend getFriend(String name) { + try (BufferedReader reader = makeApiCall(NAME_TO_UUID_URL + name)) { + if (reader == null) { + return Friend.FRIEND_NOT_FOUND; + } else { + return GsonUtils.fromJson(reader, Friend.class); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchCurrentName(Friend friend, ThrowingConsumer<String> action) { + pool.execute(() -> action.accept(getCurrentName(friend))); + } + + private static String getCurrentName(Friend friend) { + try (BufferedReader reader = makeApiCall(String.format(UUID_TO_NAME_URL, UUIDTypeAdapter.fromUUID(friend.getUuid())))) { + if (reader == null) { + return UUID_NOT_FOUND; + } else { + JsonArray nameHistoryData = new JsonParser().parse(reader).getAsJsonArray(); + if (nameHistoryData.size() > 0) { + return nameHistoryData.get(nameHistoryData.size() - 1).getAsJsonObject().get("name").getAsString(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static void fetchPlayerStatus(Friend friend, ThrowingConsumer<HyStalkingData> action) { + pool.execute(() -> action.accept(stalkPlayer(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, HyStalkingData.class); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + 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))); + } + + 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, SlothStalkingData.class); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private static BufferedReader makeApiCall(String url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")"); + + connection.getResponseCode(); + if (connection.getResponseCode() == HttpStatus.SC_NO_CONTENT) { // http status 204 + return null; + } else { + BufferedReader reader; + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) { + reader = new BufferedReader(new InputStreamReader(errorStream)); + } else { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } + return reader; + } + } +} diff --git a/src/main/java/eu/olli/cowlection/util/ChatHelper.java b/src/main/java/eu/olli/cowlection/util/ChatHelper.java new file mode 100644 index 0000000..202390f --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/ChatHelper.java @@ -0,0 +1,82 @@ +package eu.olli.cowlection.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(""); + } + + public void sendShrug(String... args) { + String chatMsg = "\u00AF\\_(\u30C4)_/\u00AF"; // ¯\\_(ツ)_/¯" + if (args.length > 0) { + chatMsg = String.join(" ", args) + " " + chatMsg; + } + Minecraft.getMinecraft().thePlayer.sendChatMessage(chatMsg); + } +} diff --git a/src/main/java/eu/olli/cowlection/util/GsonUtils.java b/src/main/java/eu/olli/cowlection/util/GsonUtils.java new file mode 100644 index 0000000..ece3faf --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/GsonUtils.java @@ -0,0 +1,28 @@ +package eu.olli.cowlection.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.util.UUIDTypeAdapter; + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.UUID; + +public final class GsonUtils { + private static final Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create(); + + private GsonUtils() { + } + + public static <T> T fromJson(String json, Type clazz) { + return gson.fromJson(json, clazz); + } + + public static <T> T fromJson(Reader json, Class<T> clazz) { + return gson.fromJson(json, clazz); + } + + public static String toJson(Object object) { + return gson.toJson(object); + } +} diff --git a/src/main/java/eu/olli/cowlection/util/ImageUtils.java b/src/main/java/eu/olli/cowlection/util/ImageUtils.java new file mode 100644 index 0000000..d6283bd --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/ImageUtils.java @@ -0,0 +1,76 @@ +package eu.olli.cowlection.util; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import eu.olli.cowlection.Cowlection; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ThreadDownloadImageData; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.ReflectionHelper; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +public class ImageUtils { + public static int getTierFromTexture(String minionSkinId) { + String textureUrl = "http://textures.minecraft.net/texture/" + minionSkinId; + MinecraftProfileTexture minionSkinTextureDetails = new MinecraftProfileTexture(textureUrl, null); + + ResourceLocation minionSkinLocation = Minecraft.getMinecraft().getSkinManager().loadSkin(minionSkinTextureDetails, MinecraftProfileTexture.Type.SKIN); + + ThreadDownloadImageData minionSkinTexture = (ThreadDownloadImageData) Minecraft.getMinecraft().getTextureManager().getTexture(minionSkinLocation); + BufferedImage minionSkinImage = ReflectionHelper.getPrivateValue(ThreadDownloadImageData.class, minionSkinTexture, "bufferedImage", "field_110560_d"); + + // extract part of the minion tier badge (center 2x2 pixel) + BufferedImage minionSkinTierBadge = minionSkinImage.getSubimage(43, 3, 2, 2); + + // reference image for tier badges: each tier is 2x2 pixel + ResourceLocation tierBadgesLocation = new ResourceLocation(Cowlection.MODID, "minion-tier-badges.png"); + + try (InputStream tierBadgesStream = Minecraft.getMinecraft().getResourceManager().getResource(tierBadgesLocation).getInputStream()) { + BufferedImage tierBadges = ImageIO.read(tierBadgesStream); + + final int maxTier = 11; + for (int tier = 0; tier < maxTier; tier++) { + BufferedImage tierBadgeRaw = tierBadges.getSubimage(tier * 2, 0, 2, 2); + if (ImageUtils.areEquals(minionSkinTierBadge, tierBadgeRaw)) { + return tier + 1; + } + } + } catch (IOException e) { + e.printStackTrace(); + return -1; + } + return -5; + } + + /** + * Compares two images pixel by pixel + * + * @param imageA the first image + * @param imageB the second image + * @return whether the images are both the same or not + * @see <a href="https://stackoverflow.com/a/29886786">Source</a> + */ + private static boolean areEquals(BufferedImage imageA, BufferedImage imageB) { + // images must be the same size + if (imageA.getWidth() != imageB.getWidth() || imageA.getHeight() != imageB.getHeight()) { + return false; + } + + int width = imageA.getWidth(); + int height = imageB.getHeight(); + + // loop over every pixel + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // compare the pixels for equality + if (imageA.getRGB(x, y) != imageB.getRGB(x, y)) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/eu/olli/cowlection/util/MooChatComponent.java b/src/main/java/eu/olli/cowlection/util/MooChatComponent.java new file mode 100644 index 0000000..0c6a141 --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/MooChatComponent.java @@ -0,0 +1,186 @@ +package eu.olli.cowlection.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/cowlection/util/TickDelay.java b/src/main/java/eu/olli/cowlection/util/TickDelay.java new file mode 100644 index 0000000..9692ce7 --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/TickDelay.java @@ -0,0 +1,29 @@ +package eu.olli.cowlection.util; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +public class TickDelay { + private Runnable task; + private int waitingTicks; + + public TickDelay(Runnable task, int ticks) { + this.task = task; + this.waitingTicks = ticks; + + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent e) { + if (e.phase == TickEvent.Phase.START) { + if (waitingTicks < 1) { + // we're done waiting! Do stuff and exit. + task.run(); + MinecraftForge.EVENT_BUS.unregister(this); + } + waitingTicks--; + } + } +} diff --git a/src/main/java/eu/olli/cowlection/util/Utils.java b/src/main/java/eu/olli/cowlection/util/Utils.java new file mode 100644 index 0000000..92f4c9e --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/Utils.java @@ -0,0 +1,250 @@ +package eu.olli.cowlection.util; + +import com.mojang.realmsclient.util.Pair; +import net.minecraft.util.EnumChatFormatting; +import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.lang3.time.DurationFormatUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.concurrent.TimeUnit; +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() { + } + + public static boolean isValidUuid(String uuid) { + return VALID_UUID_PATTERN.matcher(uuid).matches(); + } + + public static boolean isValidMcName(String username) { + return VALID_USERNAME.matcher(username).matches(); + } + + public static String fancyCase(String string) { + return WordUtils.capitalizeFully(string.replace('_', ' ')); + } + + /** + * Turn timestamp into pretty-formatted duration and date details. + * + * @param timestamp last login/logout + * @return 1st: duration between timestamp and now in words; 2nd: formatted date if time differences is >24h, otherwise null + */ + public static Pair<String, String> getDurationAsWords(long timestamp) { + long duration = System.currentTimeMillis() - timestamp; + long daysPast = TimeUnit.MILLISECONDS.toDays(duration); + + String dateFormatted = null; + if (daysPast > 1) { + dateFormatted = DateFormatUtils.format(timestamp, "dd-MMM-yyyy"); + } + + if (daysPast > 31) { + return Pair.of( + DurationFormatUtils.formatPeriod(timestamp, System.currentTimeMillis(), (daysPast > 365 ? "y 'years' " : "") + "M 'months' d 'days'"), + dateFormatted); + } else { + return Pair.of( + DurationFormatUtils.formatDurationWords(duration, true, true), + dateFormatted); + } + } + + public static String getDurationAsWord(long timestamp) { + long duration = System.currentTimeMillis() - timestamp; + long secondsPast = TimeUnit.MILLISECONDS.toSeconds(duration); + if (secondsPast < 60) { + return secondsPast + " second" + (secondsPast > 1 ? "s" : ""); + } + long minutesPast = TimeUnit.SECONDS.toMinutes(secondsPast); + if (minutesPast < 60) { + return minutesPast + " minute" + (minutesPast > 1 ? "s" : ""); + } + long hoursPast = TimeUnit.MINUTES.toHours(minutesPast); + if (hoursPast < 24) { + return hoursPast + " hour" + (hoursPast > 1 ? "s" : ""); + } + long daysPast = TimeUnit.HOURS.toDays(hoursPast); + if (daysPast < 31) { + return daysPast + " day" + (daysPast > 1 ? "s" : ""); + } + double monthsPast = daysPast / 30.5d; + if (monthsPast < 12) { + return new DecimalFormat("0.#").format(monthsPast) + " month" + (monthsPast >= 2 ? "s" : ""); + } + double yearsPast = monthsPast / 12d; + return new DecimalFormat("0.#").format(yearsPast) + " year" + (yearsPast >= 2 ? "s" : ""); + } + + public static String toRealPath(Path path) { + try { + return path.toRealPath().toString(); + } catch (IOException e) { + e.printStackTrace(); + return "file not found"; + } + } + + public static String toRealPath(File path) { + return toRealPath(path.toPath()); + } + + /** + * 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); + } + + /** + * Convert Roman numerals to their corresponding Arabic numeral + * + * @param roman Roman numeral + * @return Arabic numeral + * @see <a href="https://www.w3resource.com/javascript-exercises/javascript-math-exercise-22.php">Source</a> + */ + public static int convertRomanToArabic(String roman) { + if (roman == null) return -1; + int number = romanCharToArabic(roman.charAt(0)); + + for (int i = 1; i < roman.length(); i++) { + int current = romanCharToArabic(roman.charAt(i)); + int previous = romanCharToArabic(roman.charAt(i - 1)); + if (current <= previous) { + number += current; + } else { + number = number - previous * 2 + current; + } + } + return number; + } + + private static int romanCharToArabic(char c) { + switch (c) { + case 'I': + return 1; + case 'V': + return 5; + case 'X': + return 10; + case 'L': + return 50; + case 'C': + return 100; + case 'D': + return 500; + case 'M': + return 1000; + default: + return -1; + } + } + + /** + * Convert Arabic numerals to their corresponding Roman numerals + * + * @param number Arabic numerals + * @return Roman numerals + * @see <a href="https://stackoverflow.com/a/48357180">Source</a> + */ + public static String convertArabicToRoman(int number) { + String romanOnes = arabicToRomanChars(number % 10, "I", "V", "X"); + number /= 10; + + String romanTens = arabicToRomanChars(number % 10, "X", "L", "C"); + number /= 10; + + String romanHundreds = arabicToRomanChars(number % 10, "C", "D", "M"); + number /= 10; + + String romanThousands = arabicToRomanChars(number % 10, "M", "", ""); + + return romanThousands + romanHundreds + romanTens + romanOnes; + } + + private static String arabicToRomanChars(int n, String one, String five, String ten) { + switch (n) { + case 1: + return one; + case 2: + return one + one; + case 3: + return one + one + one; + case 4: + return one + five; + case 5: + return five; + case 6: + return five + one; + case 7: + return five + one + one; + case 8: + return five + one + one + one; + case 9: + return one + ten; + } + return ""; + } + + /** + * Get the minion tier's color for chat formatting + * + * @param tier minion tier + * @return color code corresponding to the tier + */ + public static EnumChatFormatting getMinionTierColor(int tier) { + EnumChatFormatting tierColor; + switch (tier) { + case 1: + tierColor = EnumChatFormatting.WHITE; + break; + case 2: + case 3: + case 4: + tierColor = EnumChatFormatting.GREEN; + break; + case 5: + case 6: + case 7: + tierColor = EnumChatFormatting.DARK_PURPLE; + break; + case 8: + case 9: + case 10: + tierColor = EnumChatFormatting.RED; + break; + case 11: + tierColor = EnumChatFormatting.AQUA; + break; + default: + tierColor = EnumChatFormatting.OBFUSCATED; + } + return tierColor; + } +} diff --git a/src/main/java/eu/olli/cowlection/util/VersionChecker.java b/src/main/java/eu/olli/cowlection/util/VersionChecker.java new file mode 100644 index 0000000..8f05beb --- /dev/null +++ b/src/main/java/eu/olli/cowlection/util/VersionChecker.java @@ -0,0 +1,140 @@ +package eu.olli.cowlection.util; + +import eu.olli.cowlection.Cowlection; +import eu.olli.cowlection.config.MooConfig; +import net.minecraft.client.Minecraft; +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 net.minecraft.util.IChatComponent; +import net.minecraftforge.common.ForgeModContainer; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.common.Loader; + +import java.util.concurrent.TimeUnit; + +/** + * @see ForgeVersion + */ +public class VersionChecker { + /** + * Cooldown between to update checks in minutes + */ + private static final int CHECK_COOLDOWN = 15; + private static final String CHANGELOG_URL = Cowlection.GITURL + "blob/master/CHANGELOG.md"; + private final Cowlection main; + private long lastCheck; + private String newVersion; + private String downloadUrl; + + public VersionChecker(Cowlection main) { + this.main = main; + this.lastCheck = Minecraft.getSystemTime(); + newVersion = "[newVersion]"; + downloadUrl = Cowlection.GITURL + "releases"; + } + + public boolean runUpdateCheck(boolean isCommandTriggered) { + if (isCommandTriggered || (!ForgeModContainer.disableVersionCheck && MooConfig.doUpdateCheck)) { + Runnable handleResults = () -> main.getVersionChecker().handleVersionStatus(isCommandTriggered); + + long now = Minecraft.getSystemTime(); + + // only re-run if last check was >CHECK_COOLDOWN minutes ago + if (getNextCheck() < 0) { // next allowed check is "in the past", so we're good to go + lastCheck = now; + ForgeVersion.startVersionCheck(); + + // check status after 5 seconds - hopefully that's enough to check + new TickDelay(handleResults, 5 * 20); + return true; + } else { + new TickDelay(handleResults, 1); + } + } + return false; + } + + public void handleVersionStatus(boolean isCommandTriggered) { + ForgeVersion.CheckResult versionResult = ForgeVersion.getResult(Loader.instance().activeModContainer()); + if (versionResult.target != null) { + newVersion = versionResult.target.toString(); + downloadUrl = Cowlection.GITURL + "releases/download/v" + newVersion + "/" + Cowlection.MODNAME.replace(" ", "") + "-" + newVersion + ".jar"; + } + + IChatComponent statusMsg = null; + + if (isCommandTriggered) { + if (versionResult.status == ForgeVersion.Status.UP_TO_DATE) { + // up to date + statusMsg = new ChatComponentText("\u2714 You're running the latest version (" + Cowlection.VERSION + ").").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GREEN)); + } else if (versionResult.status == ForgeVersion.Status.PENDING) { + // pending + statusMsg = new ChatComponentText("\u279C " + "Version check either failed or is still running.").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW)) + .appendSibling(new ChatComponentText("\n \u278A Check for results again in a few seconds with " + EnumChatFormatting.GOLD + "/moo version").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.YELLOW) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo version")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + "/moo version"))))) + .appendSibling(new ChatComponentText("\n \u278B Re-run update check with " + EnumChatFormatting.GOLD + "/moo update").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.YELLOW) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo update")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + "/moo update"))))); + } else if (versionResult.status == ForgeVersion.Status.FAILED) { + // check failed + statusMsg = new ChatComponentText("\u2716 Version check failed for an unknown reason. Check again in a few seconds with ").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED)) + .appendSibling(new ChatComponentText("/moo update").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.GOLD) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo update")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + "/moo update"))))); + } + } + if (versionResult.status == ForgeVersion.Status.OUTDATED || versionResult.status == ForgeVersion.Status.BETA_OUTDATED) { + // outdated + IChatComponent spacer = new ChatComponentText(" ").setChatStyle(new ChatStyle().setParentStyle(null)); + + IChatComponent text = new ChatComponentText("\u279C New version of " + EnumChatFormatting.DARK_GREEN + Cowlection.MODNAME + " " + EnumChatFormatting.GREEN + "available (" + Cowlection.VERSION + " \u27A1 " + newVersion + ")\n").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GREEN)); + + IChatComponent download = new ChatComponentText("[Download]").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.DARK_GREEN).setBold(true) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUrl)) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Download the latest version of " + Cowlection.MODNAME)))); + + IChatComponent changelog = new ChatComponentText("[Changelog]").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.DARK_AQUA).setBold(true) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, CHANGELOG_URL)) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "View changelog")))); + + IChatComponent updateInstructions = new ChatComponentText("[Update instructions]").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.GOLD).setBold(true) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo updateHelp")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Run " + EnumChatFormatting.GOLD + "/moo updateHelp")))); + + IChatComponent openModsDirectory = new ChatComponentText("\n[Open Mods directory]").setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.GREEN).setBold(true) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo directory")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Open mods directory with command " + EnumChatFormatting.GOLD + "/moo directory\n\u279C Click to open mods directory")))); + + statusMsg = text.appendSibling(download).appendSibling(spacer).appendSibling(changelog).appendSibling(spacer).appendSibling(updateInstructions).appendSibling(spacer).appendSibling(openModsDirectory); + } + + if (statusMsg != null) { + main.getChatHelper().sendMessage(statusMsg); + } + } + + public long getNextCheck() { + long cooldown = TimeUnit.MINUTES.toMillis(CHECK_COOLDOWN); + long systemTime = Minecraft.getSystemTime(); + return cooldown - (systemTime - lastCheck); + } + + public String getNewVersion() { + return newVersion; + } + + public String getDownloadUrl() { + return downloadUrl; + } +} |