Skip to content

Commit 5edd3bb

Browse files
authored
Add Infinite Timespans (SkriptLang#7956)
1 parent 80ed254 commit 5edd3bb

9 files changed

Lines changed: 194 additions & 29 deletions

File tree

src/main/java/ch/njol/skript/classes/data/DefaultOperations.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import ch.njol.skript.util.Date;
44
import ch.njol.skript.util.Timespan;
5-
import ch.njol.skript.util.Timespan.TimePeriod;
65
import ch.njol.skript.util.Utils;
76
import org.bukkit.util.Vector;
87
import org.skriptlang.skript.lang.arithmetic.Arithmetics;
@@ -92,36 +91,30 @@ public class DefaultOperations {
9291
// Timespan - Timespan
9392
Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, Timespan::add);
9493
Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, Timespan::subtract);
94+
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Timespan.class, Number.class, Timespan::divide);
9595
Arithmetics.registerDifference(Timespan.class, Timespan::difference);
9696
Arithmetics.registerDefaultValue(Timespan.class, Timespan::new);
9797

9898
// Timespan - Number
9999
// Number - Timespan
100100
Arithmetics.registerOperation(Operator.MULTIPLICATION, Timespan.class, Number.class, (left, right) -> {
101101
double scalar = right.doubleValue();
102-
if (scalar < 0 || !Double.isFinite(scalar))
102+
if (scalar < 0 || Double.isNaN(scalar))
103103
return null;
104-
double value = left.getAs(TimePeriod.MILLISECOND) * scalar;
105-
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
104+
return left.multiply(scalar);
106105
}, (left, right) -> {
107106
double scalar = left.doubleValue();
108-
if (scalar < 0 || !Double.isFinite(scalar))
107+
if (scalar < 0 || Double.isNaN(scalar))
109108
return null;
110-
double value = right.getAs(TimePeriod.MILLISECOND) * scalar;
111-
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
109+
return right.multiply(scalar);
112110
});
113111
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Number.class, (left, right) -> {
114112
double scalar = right.doubleValue();
115-
if (scalar <= 0 || !Double.isFinite(scalar))
113+
if (scalar < 0 || Double.isNaN(scalar))
116114
return null;
117-
double value = left.getAs(TimePeriod.MILLISECOND) / scalar;
118-
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
115+
return left.divide(scalar);
119116
});
120117

121-
// Timespan / Timespan = Number
122-
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Timespan.class, Number.class,
123-
(left, right) -> left.getAs(TimePeriod.MILLISECOND) / (double) right.getAs(TimePeriod.MILLISECOND));
124-
125118
// Date - Timespan
126119
Arithmetics.registerOperation(Operator.ADDITION, Date.class, Timespan.class, Date::plus);
127120
Arithmetics.registerOperation(Operator.SUBTRACTION, Date.class, Timespan.class, Date::minus);

src/main/java/ch/njol/skript/conditions/CondIsInfinite.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,30 @@
22

33
import ch.njol.skript.conditions.base.PropertyCondition;
44
import ch.njol.skript.doc.Description;
5-
import ch.njol.skript.doc.Examples;
5+
import ch.njol.skript.doc.Example;
66
import ch.njol.skript.doc.Name;
77
import ch.njol.skript.doc.Since;
8+
import ch.njol.skript.util.Timespan;
89
import org.bukkit.potion.PotionEffect;
910

10-
// This class can be expanded apon for other types if needed.
1111
@Name("Is Infinite")
12-
@Description("Checks whether potion effects are infinite.")
13-
@Examples("all of the active potion effects of the player are infinite")
12+
@Description("Checks whether potion effects or timespans are infinite.")
13+
@Example("all of the active potion effects of the player are infinite")
14+
@Example("if timespan argument is infinite:")
1415
@Since("2.7")
15-
public class CondIsInfinite extends PropertyCondition<PotionEffect> {
16+
public class CondIsInfinite extends PropertyCondition<Object> {
1617

1718
static {
18-
register(CondIsInfinite.class, "infinite", "potioneffects");
19+
register(CondIsInfinite.class, "infinite", "potioneffects/timespans");
1920
}
2021

2122
@Override
22-
public boolean check(PotionEffect potion) {
23-
return potion.isInfinite();
23+
public boolean check(Object object) {
24+
if (object instanceof PotionEffect potionEffect)
25+
return potionEffect.isInfinite();
26+
if (object instanceof Timespan timespan)
27+
return timespan.isInfinite();
28+
return false;
2429
}
2530

2631
@Override

src/main/java/ch/njol/skript/effects/EffPotion.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ protected void execute(Event event) {
107107
Timespan timespan = this.duration.getSingle(event);
108108
if (timespan == null)
109109
return;
110-
duration = (int) Math.min(timespan.getAs(Timespan.TimePeriod.TICK), Integer.MAX_VALUE);
110+
if (timespan.isInfinite()) {
111+
duration = PotionEffect.INFINITE_DURATION;
112+
} else {
113+
duration = (int) Math.min(timespan.getAs(Timespan.TimePeriod.TICK), Integer.MAX_VALUE);
114+
}
111115
}
112116
for (LivingEntity entity : entities.getArray(event)) {
113117
for (PotionEffectType potionEffectType : potionEffectTypes) {

src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
public class ExprPotionEffect extends SimpleExpression<PotionEffect> {
2929
static {
3030
Skript.registerExpression(ExprPotionEffect.class, PotionEffect.class, ExpressionType.COMBINED,
31-
"[new] potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]",
32-
"[new] ambient potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]");
31+
"[a] [new] potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]",
32+
"[a] [new] ambient potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]");
3333
}
3434

3535
@SuppressWarnings("null")
@@ -66,8 +66,9 @@ protected PotionEffect[] get(final Event e) {
6666
int ticks = 15 * 20; // 15 second default potion length
6767
if (this.timespan != null) {
6868
Timespan timespan = this.timespan.getSingle(e);
69-
if (timespan != null)
70-
ticks = (int) timespan.getAs(Timespan.TimePeriod.TICK);
69+
if (timespan != null) {
70+
ticks = timespan.isInfinite() ? PotionEffect.INFINITE_DURATION : (int) timespan.getAs(Timespan.TimePeriod.TICK);
71+
}
7172
}
7273
return new PotionEffect[]{new PotionEffect(potionEffectType, ticks, tier, ambient, particles)};
7374
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package ch.njol.skript.expressions;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.doc.Description;
5+
import ch.njol.skript.doc.Example;
6+
import ch.njol.skript.doc.Name;
7+
import ch.njol.skript.doc.Since;
8+
import ch.njol.skript.lang.Expression;
9+
import ch.njol.skript.lang.ExpressionType;
10+
import ch.njol.skript.lang.SkriptParser.ParseResult;
11+
import ch.njol.skript.lang.util.SimpleLiteral;
12+
import ch.njol.skript.util.Timespan;
13+
import ch.njol.util.Kleenean;
14+
import org.bukkit.event.Event;
15+
import org.jetbrains.annotations.Nullable;
16+
17+
@Name("An Eternity")
18+
@Description({"Represents a timespan with an infinite duration. " +
19+
"An eternity is also created when arithmetic results in a timespan larger than about 292 million years.",
20+
"Infinite timespans generally follow the rules of infinity, where most math operations do nothing. " +
21+
"However, operations that would return NaN with numbers will instead return a timespan of 0 seconds.",
22+
"Note that an eternity will often be treated as the longest duration something supports, rather than a true eternity."
23+
})
24+
@Example("set fire to the player for an eternity")
25+
@Since("INSERT VERSION")
26+
public class LitEternity extends SimpleLiteral<Timespan> {
27+
28+
static {
29+
Skript.registerExpression(LitEternity.class, Timespan.class, ExpressionType.SIMPLE,
30+
"[an] eternity",
31+
"forever",
32+
"[an] (indefinite|infinite) (duration|timespan)");
33+
}
34+
35+
public LitEternity() {
36+
super(new Timespan[]{Timespan.infinite()}, Timespan.class, true);
37+
}
38+
39+
@Override
40+
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
41+
return true;
42+
}
43+
44+
@Override
45+
public String toString(@Nullable Event event, boolean debug) {
46+
return "an eternity";
47+
}
48+
49+
}

src/main/java/ch/njol/skript/util/Timespan.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import ch.njol.util.coll.CollectionUtils;
1111
import ch.njol.yggdrasil.YggdrasilSerializable;
1212
import com.google.common.base.Preconditions;
13+
import org.jetbrains.annotations.Contract;
14+
import org.jetbrains.annotations.NotNull;
1315
import org.jetbrains.annotations.Nullable;
1416

1517
import java.time.Duration;
@@ -30,6 +32,8 @@ public class Timespan implements YggdrasilSerializable, Comparable<Timespan>, Te
3032
private static final Pattern TIMESPAN_SPLIT_PATTERN = Pattern.compile("[:.]");
3133
private static final Pattern SHORT_FORM_PATTERN = Pattern.compile("^(\\d+(?:\\.\\d+)?)([a-zA-Z]+)$");
3234

35+
private static final Noun FOREVER_NAME = new Noun("time.forever");
36+
3337
private static final List<NonNullPair<Noun, Long>> SIMPLE_VALUES = Arrays.asList(
3438
new NonNullPair<>(TimePeriod.YEAR.name, TimePeriod.YEAR.time),
3539
new NonNullPair<>(TimePeriod.MONTH.name, TimePeriod.MONTH.time),
@@ -145,15 +149,32 @@ else if (length == 3 && !hasMs || length == 4) // HH:MM:SS[.ms]
145149
return new Timespan(totalMillis);
146150
}
147151

148-
public static Timespan fromDuration(Duration duration) {
152+
/**
153+
* Creates a {@link Timespan} from the given {@link Duration}.
154+
* @param duration The duration to convert to a Timespan.
155+
* @return A new Timespan object representing the duration, based on its milliseconds.
156+
*/
157+
@Contract("_ -> new")
158+
public static @NotNull Timespan fromDuration(@NotNull Duration duration) {
149159
return new Timespan(duration.toMillis());
150160
}
151161

162+
/**
163+
* Creates a {@link Timespan} that represents an infinite duration.
164+
* @return A new Timespan object representing an infinite duration.
165+
*/
166+
@Contract(value = " -> new", pure = true)
167+
public static @NotNull Timespan infinite() {
168+
return new Timespan(Long.MAX_VALUE);
169+
}
170+
152171
public static String toString(long millis) {
153172
return toString(millis, 0);
154173
}
155174

156175
public static String toString(long millis, int flags) {
176+
if (millis == Long.MAX_VALUE)
177+
return FOREVER_NAME.toString(false);
157178
for (int i = 0; i < SIMPLE_VALUES.size() - 1; i++) {
158179
NonNullPair<Noun, Long> pair = SIMPLE_VALUES.get(i);
159180
long second1 = pair.getSecond();
@@ -241,6 +262,13 @@ public long getTicks_i() {
241262
return getAs(TimePeriod.TICK);
242263
}
243264

265+
/**
266+
* @return Whether this timespan represents an infinite timespan.
267+
*/
268+
public boolean isInfinite() {
269+
return millis == Long.MAX_VALUE;
270+
}
271+
244272
/**
245273
* @return the amount of TimePeriod this timespan represents.
246274
*/
@@ -260,7 +288,10 @@ public Duration getDuration() {
260288
* @param timespan The timespan to add to this timespan
261289
* @return a new Timespan object
262290
*/
291+
@Contract(value = "_ -> new", pure = true)
263292
public Timespan add(Timespan timespan) {
293+
if (isInfinite() || timespan.isInfinite())
294+
return Timespan.infinite();
264295
long millis = Math2.addClamped(this.millis, timespan.getAs(TimePeriod.MILLISECOND));
265296
return new Timespan(millis);
266297
}
@@ -270,17 +301,71 @@ public Timespan add(Timespan timespan) {
270301
* @param timespan The timespan to subtract from this timespan
271302
* @return a new Timespan object
272303
*/
304+
@Contract(value = "_ -> new", pure = true)
273305
public Timespan subtract(Timespan timespan) {
306+
if (isInfinite() || timespan.isInfinite())
307+
return Timespan.infinite();
274308
long millis = Math.max(0, this.millis - timespan.getAs(TimePeriod.MILLISECOND));
275309
return new Timespan(millis);
276310
}
277311

312+
/**
313+
* Safely multiplies a timespan by a non-negative scalar value.
314+
* @param scalar A non-negative (>=0) value to multiply by
315+
* @return The multiplied timespan.
316+
*/
317+
@Contract(value = "_ -> new", pure = true)
318+
public Timespan multiply(double scalar) {
319+
Preconditions.checkArgument(scalar >= 0);
320+
if (Double.isInfinite(scalar))
321+
return Timespan.infinite();
322+
double value = this.getAs(TimePeriod.MILLISECOND) * scalar;
323+
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
324+
}
325+
326+
/**
327+
* Safely divides a timespan by a non-negative scalar value.
328+
* @param scalar A non-negative (>=0) value to divide by
329+
* @return The divided timespan.
330+
*/
331+
@Contract(value = "_ -> new", pure = true)
332+
public Timespan divide(double scalar) {
333+
Preconditions.checkArgument(scalar >= 0, "Cannot divide a timespan by non-positive value");
334+
if (this.isInfinite())
335+
return Timespan.infinite();
336+
double value = this.getAs(TimePeriod.MILLISECOND) / scalar;
337+
if (Double.isNaN(value))
338+
return new Timespan(0);
339+
if (Double.isInfinite(value))
340+
return Timespan.infinite();
341+
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
342+
}
343+
344+
/**
345+
* Safely divides a timespan by another timespan.
346+
* @param other A timespan to divide by
347+
* @return The result.
348+
*/
349+
@Contract(pure = true)
350+
public double divide(Timespan other) {
351+
if (this.isInfinite()) {
352+
if (other.isInfinite())
353+
return Double.NaN;
354+
return Double.POSITIVE_INFINITY;
355+
} else if (other.isInfinite()) {
356+
return 0.0;
357+
}
358+
return this.getAs(TimePeriod.MILLISECOND) / (double) other.getAs(TimePeriod.MILLISECOND);
359+
}
360+
278361
/**
279362
* Calculates the difference between the specified timespan and this timespan.
280363
* @param timespan The timespan to get the difference of
281364
* @return a new Timespan object
282365
*/
283366
public Timespan difference(Timespan timespan) {
367+
if (isInfinite() || timespan.isInfinite())
368+
return Timespan.infinite();
284369
long millis = Math.abs(this.millis - timespan.getAs(TimePeriod.MILLISECOND));
285370
return new Timespan(millis);
286371
}

src/main/resources/lang/default.lang

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ time:
374374
short: y
375375
real: real, rl, irl
376376
minecraft: mc, minecraft
377+
forever: forever
377378

378379
# -- Chat and Wool Colours --
379380
colors:

src/test/skript/tests/regressions/7645-floating point timespan math.sk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ test "floating point timespan math":
77
assert (0.0 * 5 seconds) is 0 seconds with "failed multiplication by 0"
88
assert (5 seconds / 5) is 1 seconds with "failed integer division"
99
assert (5 seconds / 2.5) is 2 seconds with "failed floating point division"
10-
assert (5 seconds / infinity value) is not set with "Division by infinity unexpectedly returned real value"
10+
assert (5 seconds / infinity value) is 0 seconds with "Division by infinity didn't return 0 seconds"
1111

1212
assert (5 seconds * 10 ^ 308) is {@max-timespan} with "failed to clamp to the long max value"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
test "eternity":
2+
set {_a} to forever
3+
assert {_a} is forever with "comparison failed"
4+
assert {_a} is an eternity with "comparison failed"
5+
assert {_a} is an infinite duration with "comparison failed"
6+
7+
add 10 seconds to {_a}
8+
assert {_a} is an eternity with "adding changed infinity"
9+
10+
remove 10 seconds from {_a}
11+
assert {_a} is an eternity with "subtraction changed infinity"
12+
13+
assert {_a} / 10 seconds is infinity value with "division by timespan didn't return infinity"
14+
assert isNaN({_a} / {_a}) is true with "division by eternity didn't return NaN"
15+
16+
assert {_a} / 10 is an eternity with "division by number didn't return eternity"
17+
assert {_a} * 10 is an eternity with "multiplication by number didn't return eternity"
18+
19+
assert 1 second * infinity value is an eternity with "timespan * infinity wasn't an eternity"
20+
assert infinity value * 1 second is an eternity with "timespan * infinity wasn't an eternity"
21+
22+
assert -infinity value * 1 second is not set with "timespan * -infinity was set"
23+
24+
assert 1 year * 10000000000 is an eternity with "overflow did not create an eternity"
25+
26+
set {_a} to a potion effect of speed for an eternity
27+
assert {_a} is infinite with "potion effect was not infinite"

0 commit comments

Comments
 (0)