From 19ad20cc0217f950db1365c9246e1eb929f5f58f Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Sat, 15 Aug 2026 01:23:14 -0400 Subject: [PATCH 1/4] Add ms suffix support to DurationTag --- .../denizencore/objects/core/DurationTag.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java index 739302e1..bb66621a 100644 --- a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java +++ b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java @@ -33,7 +33,7 @@ public class DurationTag implements ObjectTag { // Durations are a unified and convenient way to get a 'unit of time' throughout Denizen. // Many commands and features that require a duration can be satisfied by specifying a number and unit of time, especially command arguments that are prefixed 'duration:', etc. // The unit of time can be specified by using one of the following: - // t=ticks (0.05 seconds), s=seconds, m=minutes (60 seconds), h=hours (60 minutes), d=days (24 hours), w=weeks (7 days), y=years (365 days). + // ms=milliseconds (0.001 seconds), t=ticks (0.05 seconds), s=seconds, m=minutes (60 seconds), h=hours (60 minutes), d=days (24 hours), w=weeks (7 days), y=years (365 days). // Not using a unit will imply seconds. // Examples: 10s, 50m, 1d, 20. // @@ -93,7 +93,13 @@ public static DurationTag valueOf(String string, TagContext context) { } } } - String numericString = Character.isDigit(string.charAt(string.length() - 1)) ? string : string.substring(0, string.length() - 1); + String numericString = string; + if (string.endsWith("ms")) { + numericString = string.substring(0, string.length() - 2); + } + else if (!Character.isDigit(string.charAt(string.length() - 1))) { + numericString = string.substring(0, string.length() - 1); + } // Standard DurationTag. Check the type and create new DurationTag object accordingly. try { double numVal = Double.parseDouble(numericString); @@ -119,6 +125,9 @@ else if (string.endsWith("s")) { else if (string.endsWith("t")) { return new DurationTag(numVal * 0.05); } + else if (string.endsWith("ms")) { + return new DurationTag(numVal / 1000.0); + } else if (numericString.equals(string)) { // seconds return new DurationTag(Double.parseDouble(numericString)); From 664acd221d8232e8274703f371ade16eec150c3c Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Fri, 28 Aug 2026 23:31:47 -0400 Subject: [PATCH 2/4] fix durationtag millisecond parsing --- .../denizencore/objects/core/DurationTag.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java index bb66621a..99ab0137 100644 --- a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java +++ b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java @@ -93,13 +93,7 @@ public static DurationTag valueOf(String string, TagContext context) { } } } - String numericString = string; - if (string.endsWith("ms")) { - numericString = string.substring(0, string.length() - 2); - } - else if (!Character.isDigit(string.charAt(string.length() - 1))) { - numericString = string.substring(0, string.length() - 1); - } + String numericString = Character.isDigit(string.charAt(string.length() - 1)) ? string : string.substring(0, string.endsWith("ms") ? string.length() - 2 : string.length() - 1); // Standard DurationTag. Check the type and create new DurationTag object accordingly. try { double numVal = Double.parseDouble(numericString); @@ -118,6 +112,9 @@ else if (string.endsWith("h")) { else if (string.endsWith("m")) { return new DurationTag(numVal * 60); } + else if (string.endsWith("ms")) { + return new DurationTag(numVal * 0.001); + } else if (string.endsWith("s")) { // seconds return new DurationTag(numVal); @@ -125,9 +122,6 @@ else if (string.endsWith("s")) { else if (string.endsWith("t")) { return new DurationTag(numVal * 0.05); } - else if (string.endsWith("ms")) { - return new DurationTag(numVal / 1000.0); - } else if (numericString.equals(string)) { // seconds return new DurationTag(Double.parseDouble(numericString)); From 609ffc1766c8943bbcaa9e592ed651f3acf7ba87 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Sat, 5 Sep 2026 06:32:10 -0400 Subject: [PATCH 3/4] Update duration math precision, ms formatting, and scheduler safety --- .../denizencore/objects/core/DurationTag.java | 21 ++++++++++++++++--- .../scheduling/RepeatingSchedulable.java | 3 +++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java index 99ab0137..916680a0 100644 --- a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java +++ b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java @@ -93,7 +93,8 @@ public static DurationTag valueOf(String string, TagContext context) { } } } - String numericString = Character.isDigit(string.charAt(string.length() - 1)) ? string : string.substring(0, string.endsWith("ms") ? string.length() - 2 : string.length() - 1); + int len = string.length(); + String numericString = Character.isDigit(string.charAt(len - 1)) ? string : string.substring(0, len - (string.endsWith("ms") ? 2 : 1)); // Standard DurationTag. Check the type and create new DurationTag object accordingly. try { double numVal = Double.parseDouble(numericString); @@ -198,6 +199,9 @@ public DurationTag(long ticks) { * @return the number of ticks. */ public long getTicks() { + if (seconds < 0.05 && seconds > 0) { + return 1; + } return (long) (seconds * 20); } @@ -208,6 +212,9 @@ public long getTicks() { * @return the number of ticks. */ public int getTicksAsInt() { + if (seconds < 0.05 && seconds > 0) { + return 1; + } return (int) (seconds * 20); } @@ -413,7 +420,7 @@ public static void register() { // Returns this duration minus another. // --> tagProcessor.registerStaticTag(DurationTag.class, DurationTag.class, "sub", (attribute, object, secondVal) -> { - return new DurationTag(object.getTicks() - secondVal.getTicks()); + return new DurationTag(object.seconds - secondVal.seconds); }); // <--[tag] @@ -423,7 +430,7 @@ public static void register() { // Returns this duration plus another. // --> tagProcessor.registerStaticTag(DurationTag.class, DurationTag.class, "add", (attribute, object, secondVal) -> { - return new DurationTag(object.getTicks() + secondVal.getTicks()); + return new DurationTag(object.seconds + secondVal.seconds); }); // <--[tag] @@ -553,10 +560,18 @@ public String formatted(boolean words) { if (seconds > 0 && minutes < 10 && hours == 0 && days == 0 && years == 0) { timeString += seconds + (words ? " second" + autoS(seconds) : "s "); } + long millis = Math.round(secondsCopy * 1000) % 1000; + if (millis > 0 && (millis % 50 != 0) && minutes < 10 && hours == 0 && days == 0 && years == 0) { + timeString += millis + (words ? " millisecond" + autoS(millis) : "ms "); + } if (timeString.isEmpty()) { if (secondsCopy == 0) { timeString = "forever"; } + else if (Math.round(secondsCopy * 1000) % 50 != 0) { + long ms = Math.round(secondsCopy * 1000); + timeString = ms + (words ? " millisecond" + autoS(ms) : "ms"); + } else { timeString = ((double) ((long) (secondsCopy * 100)) / 100d) + "s"; } diff --git a/src/main/java/com/denizenscript/denizencore/utilities/scheduling/RepeatingSchedulable.java b/src/main/java/com/denizenscript/denizencore/utilities/scheduling/RepeatingSchedulable.java index 7657165a..ae846086 100644 --- a/src/main/java/com/denizenscript/denizencore/utilities/scheduling/RepeatingSchedulable.java +++ b/src/main/java/com/denizenscript/denizencore/utilities/scheduling/RepeatingSchedulable.java @@ -4,6 +4,9 @@ public class RepeatingSchedulable extends Schedulable { public RepeatingSchedulable(Runnable runnable, float fireRate) { run = runnable; + if (fireRate > 0 && fireRate < 0.05f) { + fireRate = 0.05f; + } fireEverySeconds = fireRate; secondsLeft = fireRate; } From 8749207e0e44286c9e7ad42762739be08d20c9e8 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Sat, 5 Sep 2026 06:41:08 -0400 Subject: [PATCH 4/4] Use BigDecimal in DurationTag add and sub to eliminate floating-point precision artifacts --- .../denizenscript/denizencore/objects/core/DurationTag.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java index 916680a0..c6bc5fc8 100644 --- a/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java +++ b/src/main/java/com/denizenscript/denizencore/objects/core/DurationTag.java @@ -420,7 +420,7 @@ public static void register() { // Returns this duration minus another. // --> tagProcessor.registerStaticTag(DurationTag.class, DurationTag.class, "sub", (attribute, object, secondVal) -> { - return new DurationTag(object.seconds - secondVal.seconds); + return new DurationTag(java.math.BigDecimal.valueOf(object.seconds).subtract(java.math.BigDecimal.valueOf(secondVal.seconds)).doubleValue()); }); // <--[tag] @@ -430,7 +430,7 @@ public static void register() { // Returns this duration plus another. // --> tagProcessor.registerStaticTag(DurationTag.class, DurationTag.class, "add", (attribute, object, secondVal) -> { - return new DurationTag(object.seconds + secondVal.seconds); + return new DurationTag(java.math.BigDecimal.valueOf(object.seconds).add(java.math.BigDecimal.valueOf(secondVal.seconds)).doubleValue()); }); // <--[tag]