diff --git a/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java b/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java index c129b38..86039ea 100644 --- a/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java +++ b/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java @@ -210,7 +210,8 @@ public class CommandToolStats implements TabExecutor { if (flightTime != null) { if (toolStats.config.getBoolean("enabled.flight-time")) { - Component line = toolStats.configTools.formatLore("flight-time", "{time}", toolStats.numberFormat.formatDouble((double) flightTime / 1000)); + Map flightTimeFormatted = toolStats.numberFormat.formatTime(flightTime); + Component line = toolStats.configTools.formatLoreMultiplePlaceholders("flight-time", flightTimeFormatted); lore.add(line); } } diff --git a/src/main/java/lol/hyper/toolstats/tools/ItemLore.java b/src/main/java/lol/hyper/toolstats/tools/ItemLore.java index 4ac83f8..478c08a 100644 --- a/src/main/java/lol/hyper/toolstats/tools/ItemLore.java +++ b/src/main/java/lol/hyper/toolstats/tools/ItemLore.java @@ -27,6 +27,7 @@ import org.bukkit.persistence.PersistentDataType; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class ItemLore { @@ -767,8 +768,8 @@ public class ItemLore { } container.remove(toolStats.flightTime); if (meta.hasLore()) { - String oldFlightTimeFormatted = toolStats.numberFormat.formatDouble(flightTime); - Component lineToRemove = toolStats.configTools.formatLore("flight-time", "{time}", oldFlightTimeFormatted); + Map oldFlightTimeFormatted = toolStats.numberFormat.formatTime(flightTime); + Component lineToRemove = toolStats.configTools.formatLoreMultiplePlaceholders("flight-time", oldFlightTimeFormatted); List newLore = removeLore(meta.lore(), lineToRemove); meta.lore(newLore); } @@ -815,10 +816,10 @@ public class ItemLore { } container.set(toolStats.flightTime, PersistentDataType.LONG, flightTime + duration); - String oldFlightFormatted = toolStats.numberFormat.formatDouble((double) flightTime / 1000); - String newFlightFormatted = toolStats.numberFormat.formatDouble((double) (flightTime + duration) / 1000); - Component oldLine = toolStats.configTools.formatLore("flight-time", "{time}", oldFlightFormatted); - Component newLine = toolStats.configTools.formatLore("flight-time", "{time}", newFlightFormatted); + Map oldFlightFormatted = toolStats.numberFormat.formatTime(flightTime); + Map newFlightFormatted = toolStats.numberFormat.formatTime(flightTime + duration); + Component oldLine = toolStats.configTools.formatLoreMultiplePlaceholders("flight-time", oldFlightFormatted); + Component newLine = toolStats.configTools.formatLoreMultiplePlaceholders("flight-time", newFlightFormatted); if (oldLine == null || newLine == null) { return null; } diff --git a/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java b/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java index 24aa3ec..1366ae1 100644 --- a/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java +++ b/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java @@ -23,7 +23,9 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; public class NumberFormat { @@ -136,4 +138,59 @@ public class NumberFormat { public String formatDate(Date date) { return DATE_FORMAT.format(date); } + + /** + * Returns a human readable form of time in milliseconds. + * E.g. given 3752348000L outputs 1 years, 5 months, 3 days, 14 hours, 12 minutes, 28 seconds. + * @param time The time in ms. + * @return Map with units as keys and time value, e.g. "years" (key) -> 1 (value) + */ + public Map formatTime(Long time) { + final int SECONDS_PER_MINUTE = 60; + final int MINUTES_PER_HOUR = 60; + final int HOURS_PER_DAY = 24; + final int DAYS_PER_MONTH = 30; // Approximation + final int DAYS_PER_YEAR = 365; // Approximation + + long totalSeconds = time / 1000; + + Map timeUnits = new HashMap<>(); + + long years = totalSeconds / (DAYS_PER_YEAR * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + if (years > 0) { + timeUnits.put("years", Long.toString(years)); + } + totalSeconds %= (DAYS_PER_YEAR * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + long months = totalSeconds / (DAYS_PER_MONTH * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + if (months > 0) { + timeUnits.put("months", Long.toString(months)); + } + totalSeconds %= (DAYS_PER_MONTH * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + long days = totalSeconds / (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + if (days > 0) { + timeUnits.put("days", Long.toString(days)); + } + totalSeconds %= (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + long hours = totalSeconds / (MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + if (hours > 0) { + timeUnits.put("hours", Long.toString(hours)); + } + totalSeconds %= (MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + long minutes = totalSeconds / SECONDS_PER_MINUTE; + if (minutes > 0) { + timeUnits.put("minutes", Long.toString(minutes)); + } + totalSeconds %= SECONDS_PER_MINUTE; + + long seconds = totalSeconds; + if (seconds > 0 || timeUnits.isEmpty()) { // Always include seconds if everything else is zero + timeUnits.put("seconds", Long.toString(seconds)); + } + + return timeUnits; + } } diff --git a/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java b/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java index 4d0139e..e8e3fe5 100644 --- a/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java +++ b/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java @@ -27,6 +27,7 @@ import org.bukkit.Material; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -133,6 +134,65 @@ public class ConfigTools { return component.decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE); } + /** + * Format a string with several placeholders to be ready for lore usage. + * + * @param configName The message to use. + * @param placeHoldersValues Map containing placeholders names as keys and values. + * @return Formatted string, null if the configName doesn't exist. + */ + public Component formatLoreMultiplePlaceholders(String configName, Map placeHoldersValues) { + String lore = toolStats.config.getString("messages." + configName); + if (lore == null) { + toolStats.logger.warning("Unable to find config message for: messages." + configName); + return null; + } + + // if the config message is empty, don't send it + if (lore.isEmpty()) { + return null; + } + + Pattern pattern = Pattern.compile("\\{([^}]+)\\}(\\S*)\\s*"); + Matcher matcher = pattern.matcher(lore); + + StringBuilder result = new StringBuilder(); + int lastEnd = 0; + + while (matcher.find()) { + String placeholder = matcher.group(1); + String unit = matcher.group(2); + + result.append(lore, lastEnd, matcher.start()); + + if (placeHoldersValues.containsKey(placeholder)) { + result.append(placeHoldersValues.get(placeholder)).append(unit).append(" "); + } + + // Update lastEnd to end of the match + lastEnd = matcher.end(); + } + if (lastEnd < lore.length()) { + result.append(lore.substring(lastEnd)); + } + + Component component; + // Clean output text + String outputText = result.toString().replaceAll("\\s+", " ").trim(); + + // if we match the old color codes, then format them as so + Matcher hexMatcher = CONFIG_HEX_PATTERN.matcher(outputText); + Matcher colorMatcher = COLOR_CODES.matcher(outputText); + if (hexMatcher.find() || colorMatcher.find()) { + component = LegacyComponentSerializer.legacyAmpersand().deserialize(outputText); + } else { + // otherwise format them normally + component = MiniMessage.miniMessage().deserialize(outputText); + } + + return component.decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE); + } + /** * Format a string from the config. * diff --git a/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java b/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java index 244d17d..51dbf58 100644 --- a/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java +++ b/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java @@ -62,4 +62,4 @@ public class ConfigUpdater { version11.update(); } } -} +} \ No newline at end of file diff --git a/src/main/java/lol/hyper/toolstats/tools/config/versions/Version11.java b/src/main/java/lol/hyper/toolstats/tools/config/versions/Version11.java index 71fd4c2..b433add 100644 --- a/src/main/java/lol/hyper/toolstats/tools/config/versions/Version11.java +++ b/src/main/java/lol/hyper/toolstats/tools/config/versions/Version11.java @@ -86,6 +86,9 @@ public class Version11 { toolStats.logger.info("Adding enabled.damage-done.bow to config.yml"); toolStats.logger.info("Adding enabled.damage-done.mace to config.yml"); + toolStats.logger.info("Updating entry for messages.flight-time"); + toolStats.config.set("messages.flight-time", "&7Flight time: &8{years}y {months}m {days}d {hours}h {minutes}m {seconds}s"); + // save the config and reload it try { toolStats.config.save("plugins" + File.separator + "ToolStats" + File.separator + "config.yml"); @@ -96,4 +99,4 @@ public class Version11 { toolStats.loadConfig(); toolStats.logger.info("Config has been updated to version 11. A copy of version 10 has been saved as config-10.yml"); } -} +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 38a55d1..fd74f79 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -196,7 +196,7 @@ messages: dropped-by: "&7Dropped by: &8{name}" # name will be player/mob name damage-taken: "&7Damage taken: &8{damage}" arrows-shot: "&7Arrows shot: &8{arrows}" - flight-time: "&7Flight time: &8{time}" + flight-time: "&7Flight time: &8{years}y {months}m {days}d {hours}h {minutes}m {seconds}s" damage-done: "&7Damage done: &8{damage}" # Set display name for mobs. See: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html mobs: