From d6755492097e16e6d6fae5eececada0f371c8f3a Mon Sep 17 00:00:00 2001 From: sebampuero Date: Fri, 14 Mar 2025 22:46:51 +0100 Subject: [PATCH] feat: add format lore that replaces several placeholders with a map --- .../lol/hyper/toolstats/tools/ItemLore.java | 13 ++-- .../hyper/toolstats/tools/NumberFormat.java | 38 +++++++--- .../toolstats/tools/config/ConfigTools.java | 70 +++++++++++++++++++ 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/src/main/java/lol/hyper/toolstats/tools/ItemLore.java b/src/main/java/lol/hyper/toolstats/tools/ItemLore.java index f684693..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.formatTime(flightTime); - String newFlightFormatted = toolStats.numberFormat.formatTime(flightTime + duration); - 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 8ed6bc4..83b9c36 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 { @@ -138,39 +140,57 @@ public class NumberFormat { } /** - * Formats time in milliseconds in a human readable format. - * @param time The time in milliseconds to format. - * @return The time in a human readable format. + * Returns a human readable form of time in milliseconds. + * E.g. given 3752348000L outputs 1 years, 5 months, 2 weeks, 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 String formatTime(Long time) { + 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_WEEK = 7; 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 weeks = totalSeconds / (DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); - totalSeconds %= (DAYS_PER_WEEK * 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; - return ""; + 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..a6d89fe 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,75 @@ 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("\\{([^}]+)\\}"); + Matcher matcher = pattern.matcher(lore); + + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String placeholder = matcher.group(1); + if (placeHoldersValues.containsKey(placeholder)) { + matcher.appendReplacement(result, placeHoldersValues.get(placeholder)); + } else { + // Placeholder not found in our time values, so remove it and any unit suffix + // Find the next non-alphanumeric character after this placeholder to remove the unit + int end = matcher.end(); + while (end < lore.length() && + !Character.isWhitespace(lore.charAt(end)) && + !lore.substring(end, end + 1).matches("[^a-zA-Z]")) { + end++; + } + + matcher.appendReplacement(result, ""); + + // Remove trailing space if there is one + if (end < lore.length() && Character.isWhitespace(lore.charAt(end))) { + // Skip this space in the next append + end++; + } + + // Adjust region to char after skipped placeholder + matcher.region(end, lore.length()); + } + } + + matcher.appendTail(result); + + 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. *