Skip to content

Add infinite timespans #7956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: dev/feature
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import ch.njol.skript.util.Date;
import ch.njol.skript.util.Timespan;
import ch.njol.skript.util.Timespan.TimePeriod;
import ch.njol.skript.util.Utils;
import org.bukkit.util.Vector;
import org.skriptlang.skript.lang.arithmetic.Arithmetics;
Expand Down Expand Up @@ -92,36 +91,30 @@ public class DefaultOperations {
// Timespan - Timespan
Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, Timespan::add);
Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, Timespan::subtract);
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Timespan.class, Number.class, Timespan::divide);
Arithmetics.registerDifference(Timespan.class, Timespan::difference);
Arithmetics.registerDefaultValue(Timespan.class, Timespan::new);

// Timespan - Number
// Number - Timespan
Arithmetics.registerOperation(Operator.MULTIPLICATION, Timespan.class, Number.class, (left, right) -> {
double scalar = right.doubleValue();
if (scalar < 0 || !Double.isFinite(scalar))
if (scalar < 0 || Double.isNaN(scalar))
return null;
double value = left.getAs(TimePeriod.MILLISECOND) * scalar;
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
return left.multiply(scalar);
}, (left, right) -> {
double scalar = left.doubleValue();
if (scalar < 0 || !Double.isFinite(scalar))
if (scalar < 0 || Double.isNaN(scalar))
return null;
double value = right.getAs(TimePeriod.MILLISECOND) * scalar;
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
return right.multiply(scalar);
});
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Number.class, (left, right) -> {
double scalar = right.doubleValue();
if (scalar <= 0 || !Double.isFinite(scalar))
if (scalar < 0 || Double.isNaN(scalar))
return null;
double value = left.getAs(TimePeriod.MILLISECOND) / scalar;
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
return left.divide(scalar);
});

// Timespan / Timespan = Number
Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Timespan.class, Number.class,
(left, right) -> left.getAs(TimePeriod.MILLISECOND) / (double) right.getAs(TimePeriod.MILLISECOND));

// Date - Timespan
Arithmetics.registerOperation(Operator.ADDITION, Date.class, Timespan.class, Date::plus);
Arithmetics.registerOperation(Operator.SUBTRACTION, Date.class, Timespan.class, Date::minus);
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/ch/njol/skript/conditions/CondIsInfinite.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,30 @@

import ch.njol.skript.conditions.base.PropertyCondition;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.util.Timespan;
import org.bukkit.potion.PotionEffect;

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

static {
register(CondIsInfinite.class, "infinite", "potioneffects");
register(CondIsInfinite.class, "infinite", "potioneffects/timespans");
}

@Override
public boolean check(PotionEffect potion) {
return potion.isInfinite();
public boolean check(Object object) {
if (object instanceof PotionEffect potionEffect)
return potionEffect.isInfinite();
if (object instanceof Timespan timespan)
return timespan.isInfinite();
return false;
}

@Override
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/ch/njol/skript/effects/EffPotion.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ protected void execute(Event event) {
Timespan timespan = this.duration.getSingle(event);
if (timespan == null)
return;
duration = (int) Math.min(timespan.getAs(Timespan.TimePeriod.TICK), Integer.MAX_VALUE);
if (timespan.isInfinite()) {
duration = -1;
} else {
duration = (int) Math.min(timespan.getAs(Timespan.TimePeriod.TICK), Integer.MAX_VALUE);
}
}
for (LivingEntity entity : entities.getArray(event)) {
for (PotionEffectType potionEffectType : potionEffectTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
public class ExprPotionEffect extends SimpleExpression<PotionEffect> {
static {
Skript.registerExpression(ExprPotionEffect.class, PotionEffect.class, ExpressionType.COMBINED,
"[new] potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]",
"[new] ambient potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]");
"[a] [new] potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]",
"[a] [new] ambient potion effect of %potioneffecttype% [potion] [[[of] tier] %-number%] [(1¦without particles)] [for %-timespan%]");
}

@SuppressWarnings("null")
Expand Down Expand Up @@ -66,8 +66,9 @@ protected PotionEffect[] get(final Event e) {
int ticks = 15 * 20; // 15 second default potion length
if (this.timespan != null) {
Timespan timespan = this.timespan.getSingle(e);
if (timespan != null)
ticks = (int) timespan.getAs(Timespan.TimePeriod.TICK);
if (timespan != null) {
ticks = timespan.isInfinite() ? -1 : (int) timespan.getAs(Timespan.TimePeriod.TICK);
}
}
return new PotionEffect[]{new PotionEffect(potionEffectType, ticks, tier, ambient, particles)};
}
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/ch/njol/skript/expressions/LitEternity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch.njol.skript.expressions;

import ch.njol.skript.Skript;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.util.SimpleLiteral;
import ch.njol.skript.util.Timespan;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;

@Name("An Eternity")
@Description({"Represents a timespan with an infinite duration. " +
"An eternity is also created when arithmetic results in a timespan larger than about 292 million years.",
"Infinite timespans generally follow the rules of infinity, where most math operations do nothing. " +
"However, operations that would return NaN with numbers will instead return a timespan of 0 seconds.",
"Note that an eternity will often be treated as the longest duration something supports, rather than a true eternity."
})
@Example("set fire to the player for an eternity")
@Since("INSERT VERSION")
public class LitEternity extends SimpleLiteral<Timespan> {

static {
Skript.registerExpression(LitEternity.class, Timespan.class, ExpressionType.SIMPLE,
"[an] eternity",
"forever",
"[an] (indefinite|infinite) (duration|timespan)"
);
}

public LitEternity() {
super(new Timespan[]{Timespan.infinite()}, Timespan.class, true);
}

@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
return true;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "an eternity";
}

}
78 changes: 77 additions & 1 deletion src/main/java/ch/njol/skript/util/Timespan.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import ch.njol.util.coll.CollectionUtils;
import ch.njol.yggdrasil.YggdrasilSerializable;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

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

private static final Noun FOREVER_NAME = new Noun("time.forever");

private static final List<NonNullPair<Noun, Long>> SIMPLE_VALUES = Arrays.asList(
new NonNullPair<>(TimePeriod.YEAR.name, TimePeriod.YEAR.time),
new NonNullPair<>(TimePeriod.MONTH.name, TimePeriod.MONTH.time),
Expand Down Expand Up @@ -145,15 +149,23 @@ else if (length == 3 && !hasMs || length == 4) // HH:MM:SS[.ms]
return new Timespan(totalMillis);
}

public static Timespan fromDuration(Duration duration) {
@Contract("_ -> new")
public static @NotNull Timespan fromDuration(@NotNull Duration duration) {
return new Timespan(duration.toMillis());
}

@Contract(value = " -> new", pure = true)
public static @NotNull Timespan infinite() {
return new Timespan(Long.MAX_VALUE);
}

public static String toString(long millis) {
return toString(millis, 0);
}

public static String toString(long millis, int flags) {
if (millis == Long.MAX_VALUE)
return FOREVER_NAME.toString(false);
for (int i = 0; i < SIMPLE_VALUES.size() - 1; i++) {
NonNullPair<Noun, Long> pair = SIMPLE_VALUES.get(i);
long second1 = pair.getSecond();
Expand Down Expand Up @@ -241,6 +253,13 @@ public long getTicks_i() {
return getAs(TimePeriod.TICK);
}

/**
* @return Whether this timespan represents an infinite timespan.
*/
public boolean isInfinite() {
return millis == Long.MAX_VALUE;
}

/**
* @return the amount of TimePeriod this timespan represents.
*/
Expand All @@ -260,7 +279,10 @@ public Duration getDuration() {
* @param timespan The timespan to add to this timespan
* @return a new Timespan object
*/
@Contract(value = "_ -> new", pure = true)
public Timespan add(Timespan timespan) {
if (isInfinite() || timespan.isInfinite())
return Timespan.infinite();
long millis = Math2.addClamped(this.millis, timespan.getAs(TimePeriod.MILLISECOND));
return new Timespan(millis);
}
Expand All @@ -270,17 +292,71 @@ public Timespan add(Timespan timespan) {
* @param timespan The timespan to subtract from this timespan
* @return a new Timespan object
*/
@Contract(value = "_ -> new", pure = true)
public Timespan subtract(Timespan timespan) {
if (isInfinite() || timespan.isInfinite())
return Timespan.infinite();
long millis = Math.max(0, this.millis - timespan.getAs(TimePeriod.MILLISECOND));
return new Timespan(millis);
}

/**
* Safely multiplies a timespan by a non-negative scalar value.
* @param scalar A non-negative (>=0) value to multiply by
* @return The multiplied timespan.
*/
@Contract(value = "_ -> new", pure = true)
public Timespan multiply(double scalar) {
Preconditions.checkArgument(scalar >= 0);
if (Double.isInfinite(scalar))
return Timespan.infinite();
double value = this.getAs(TimePeriod.MILLISECOND) * scalar;
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
}

/**
* Safely divides a timespan by a non-negative scalar value.
* @param scalar A non-negative (>=0) value to divide by
* @return The divided timespan.
*/
@Contract(value = "_ -> new", pure = true)
public Timespan divide(double scalar) {
Preconditions.checkArgument(scalar >= 0, "Cannot divide a timespan by non-positive value");
if (this.isInfinite())
return Timespan.infinite();
double value = this.getAs(TimePeriod.MILLISECOND) / scalar;
if (Double.isNaN(value))
return new Timespan(0);
if (Double.isInfinite(value))
return Timespan.infinite();
return new Timespan((long) Math.min(value, Long.MAX_VALUE));
}

/**
* Safely divides a timespan by another timespan.
* @param other A timespan to divide by
* @return The result.
*/
@Contract(pure = true)
public double divide(Timespan other) {
if (this.isInfinite()) {
if (other.isInfinite())
return Double.NaN;
return Double.POSITIVE_INFINITY;
} else if (other.isInfinite()) {
return 0.0;
}
return this.getAs(TimePeriod.MILLISECOND) / (double) other.getAs(TimePeriod.MILLISECOND);
}

/**
* Calculates the difference between the specified timespan and this timespan.
* @param timespan The timespan to get the difference of
* @return a new Timespan object
*/
public Timespan difference(Timespan timespan) {
if (isInfinite() || timespan.isInfinite())
return Timespan.infinite();
long millis = Math.abs(this.millis - timespan.getAs(TimePeriod.MILLISECOND));
return new Timespan(millis);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/lang/default.lang
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ time:
short: y
real: real, rl, irl
minecraft: mc, minecraft
forever: forever

# -- Chat and Wool Colours --
colors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ test "floating point timespan math":
assert (0.0 * 5 seconds) is 0 seconds with "failed multiplication by 0"
assert (5 seconds / 5) is 1 seconds with "failed integer division"
assert (5 seconds / 2.5) is 2 seconds with "failed floating point division"
assert (5 seconds / infinity value) is not set with "Division by infinity unexpectedly returned real value"
assert (5 seconds / infinity value) is 0 seconds with "Division by infinity didn't return 0 seconds"

assert (5 seconds * 10 ^ 308) is {@max-timespan} with "failed to clamp to the long max value"
28 changes: 28 additions & 0 deletions src/test/skript/tests/syntaxes/expressions/LitEternity.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
test "eternity":
set {_a} to forever
assert {_a} is forever with "comparison failed"
assert {_a} is an eternity with "comparison failed"
assert {_a} is an infinite duration with "comparison failed"

add 10 seconds to {_a}
assert {_a} is an eternity with "adding changed infinity"

remove 10 seconds from {_a}
assert {_a} is an eternity with "subtraction changed infinity"

assert {_a} / 10 seconds is infinity value with "division by timespan didn't return infinity"
assert isNaN({_a} / {_a}) is true with "division by eternity didn't return NaN"

assert {_a} / 10 is an eternity with "division by number didn't return eternity"
assert {_a} * 10 is an eternity with "multiplication by number didn't return eternity"

assert 1 second * infinity value is an eternity with "timespan * infinity wasn't an eternity"
assert infinity value * 1 second is an eternity with "timespan * infinity wasn't an eternity"

assert -infinity value * 1 second is not set with "timespan * -infinity was set"

assert 1 year * 10000000000 is an eternity with "overflow did not create an eternity"

set {_a} to a potion effect of speed for an eternity
assert {_a} is infinite with "potion effect was not infinite"
assert {_a} is infinite