diff --git a/src/main/java/ch/njol/skript/expressions/ExprClamp.java b/src/main/java/ch/njol/skript/expressions/ExprClamp.java new file mode 100644 index 00000000000..41be6e369b6 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprClamp.java @@ -0,0 +1,143 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +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.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Clamp") +@Description("Clamps one or more values between two numbers.") +@Examples({ + "5 clamped between 0 and 10 # result = 5", + "5.5 clamped between 0 and 5 # result = 5", + "0.25 clamped between 0 and 0.5 # result = 0.25", + "(5, 0, 10, 9, 13) clamped between 7 and 10 # result = (7, 7, 10, 9, 10)", + "set {_clamped::*} to {_values::*} clamped between 0 and 10", + "", + "3 clamped below 5 # result = 3", + "3.2 clamped below 2 # result = 2", + "(-1, 3, 0.5, 9) clamped below 3 # result = (-1, 3, 0.5, 3)", + "", + "6.5 clamped above 6 # result = 6.5", + "4 clamped above 5.5 # result = 5.5", + "(3.14, 1, -9.8, 2.7) clamped above 2.5 # result = (3.14, 2.5, 2.5, 2.7)", + "set {_not negative} to {_input} clamped above 0" +}) +@Since("INSERT VERSION") +public class ExprClamp extends SimpleExpression { + + static { + Skript.registerExpression(ExprClamp.class, Number.class, ExpressionType.COMBINED, + "%numbers% clamped between %number% and %number%", + "%numbers% clamped below %number%", + "%numbers% clamped above %number%"); + } + + private enum Mode { + BOTH { + @Override + public String toString() { + return "between"; + } + }, + BELOW { + @Override + public String toString() { + return "below"; + } + }, + ABOVE { + @Override + public String toString() { + return "above"; + } + } + } + + private Expression values, minExpr, maxExpr; + private Mode mode; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + mode = Mode.values()[matchedPattern]; + values = (Expression) expressions[0]; + switch (mode) { + case BOTH: + maxExpr = (Expression) expressions[2]; + case ABOVE: + minExpr = (Expression) expressions[1]; + break; + case BELOW: + maxExpr = (Expression) expressions[1]; + } + return true; + } + + @Nullable + @Override + protected Number[] get(Event event) { + Number[] numbers = values.getArray(event); + Double[] clampedValues = new Double[numbers.length]; + double min = Double.NEGATIVE_INFINITY; + double max = Double.POSITIVE_INFINITY; + if (mode == Mode.ABOVE || mode == Mode.BOTH) { + min = minExpr.getOptionalSingle(event).orElse(Double.NEGATIVE_INFINITY).doubleValue(); + } + if (mode == Mode.BELOW || mode == Mode.BOTH) { + max = maxExpr.getOptionalSingle(event).orElse(Double.POSITIVE_INFINITY).doubleValue(); + } + // Make sure the min and max are in the correct order + double trueMin = Math.min(min, max); + double trueMax = Math.max(min, max); + for (int i = 0; i < numbers.length; i++) { + double value = numbers[i].doubleValue(); + clampedValues[i] = Math.max(Math.min(value, trueMax), trueMin); + } + return clampedValues; + } + + @Override + public boolean isSingle() { + return values.isSingle(); + } + + @Override + public Class getReturnType() { + return Number.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return values.toString(event, debug) + " clamped " + mode + " " + + ((mode == Mode.ABOVE || mode == Mode.BOTH) ? minExpr.toString(event, debug) : "") + + ((mode == Mode.BOTH) ? " and " : "") + + ((mode == Mode.BELOW || mode == Mode.BOTH) ? maxExpr.toString(event, debug) : ""); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprClamp.sk b/src/test/skript/tests/syntaxes/expressions/ExprClamp.sk new file mode 100644 index 00000000000..83ee267e7c3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprClamp.sk @@ -0,0 +1,119 @@ +test "clamp expression": + # Upper and lower limits + + # Normal Cases + assert 1 clamped between 0 and 2 is 1 with error "(single ints) min < value < max" + assert 1 clamped between 1 and 2 is 1 with error "(single ints) min = value < max" + assert 2 clamped between 1 and 2 is 2 with error "(single ints) min < value = max" + assert 0 clamped between 1 and 2 is 1 with error "(single ints) value < min < max" + assert 3 clamped between 1 and 2 is 2 with error "(single ints) min < max < value" + assert 3 clamped between 2 and 1 is 2 with error "(single ints) max < min < value" + + assert 1.999 clamped between 0.0 and 2.0 is 1.999 with error "(single floats) min < value < max" + assert 1.999 clamped between 1.999 and 2.0 is 1.999 with error "(single floats) min = value < max" + assert 2.0 clamped between 1.999 and 2.0 is 2.0 with error "(single floats) min < value = max" + assert 0.0 clamped between 1.999 and 2.0 is 1.999 with error "(single floats) value < min < max" + assert 3.0 clamped between 1.999 and 2.0 is 2.0 with error "(single floats) min < max < value" + assert 2.999 clamped between 2.0 and 1.999 is 2.0 with error "(single floats) max < min < value" + + # Lists + set {_expected::*} to (0, 0, 1, 2, 2, and 2) + # this is dumb but comparing the lists directly didn't work + set {_got::*} to (-1, 0, 1, 2, 3, and 4) clamped between 0 and 2 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple ints)" + set {_got::*} to (-1.999, 0.0, 1.0, 2.0, 3.0, and 4.0) clamped between 0.0 and 2.0 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple floats)" + + # Edge Cases + assert 1 clamped between {_null} and 2 is 1 with error "(single ints) min = null" + assert 2 clamped between 1 and {_null} is 2 with error "(single ints) max = null" + assert {_null} clamped between 1 and 2 is not set with error "(single ints) value = null" + assert isNaN(1 clamped between 0 and NaN value) is true with error "(single ints) min < value < NaN" + assert isNaN(1 clamped between NaN value and 2) is true with error "(single ints) NaN < value < max" + assert isNaN(NaN value clamped between 1 and 2) is true with error "(single ints) min < NaN < max" + assert infinity value clamped between 1 and 2 is 2 with error "(single ints) min < infinity < max" + assert -infinity value clamped between 1 and 2 is 1 with error "(single ints) min < -infinity < max" + assert 1 clamped between 0 and infinity value is 1 with error "(single ints) min < value < infinity" + assert 1 clamped between -infinity value and 2 is 1 with error "(single ints) -infinity < value < max" + + set {_expected::*} to (NaN value, 0.0, and 2.0) + set {_got::*} to ({_null}, NaN value, -infinity value, infinity value) clamped between 0.0 and 2.0 + assert isNaN({_got::1}) is true with error "(edge cases list) NaN" + assert {_got::2} is {_expected::2} with error "(edge cases list) -infinity" + assert {_got::3} is {_expected::3} with error "(edge cases list) infinity" + + + # Upper limits only (below) + + # Normal Cases + assert 1 clamped below 2 is 1 with error "(single ints) value < max" + assert 2 clamped below 2 is 2 with error "(single ints) value = max" + assert 3 clamped below 2 is 2 with error "(single ints) max < value" + + assert 1.999 clamped below 2.0 is 1.999 with error "(single floats) value < max" + assert 2.0 clamped below 2.0 is 2.0 with error "(single floats) value = max" + assert 3.0 clamped below 2.0 is 2.0 with error "(single floats) min < max < value" + + # Lists + set {_expected::*} to (-1, 0, 1, 2, 2, and 2) + # this is dumb but comparing the lists directly didn't work + set {_got::*} to (-1, 0, 1, 2, 3, and 4) clamped below 2 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple ints)" + set {_expected::*} to (-1.999, 0, 1, 2, 2, and 2) + set {_got::*} to (-1.999, 0.0, 1.0, 2.0, 3.0, and 4.0) clamped below 2.0 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple floats)" + + # Edge Cases + assert 2 clamped below {_null} is 2 with error "(single ints) max = null" + assert {_null} clamped below 2 is not set with error "(single ints) value = null" + assert isNaN(1 clamped below NaN value) is true with error "(single ints) value < NaN" + assert isNaN(NaN value clamped below 2) is true with error "(single ints) NaN < max" + assert infinity value clamped below 2 is 2 with error "(single ints) infinity < max" + assert -infinity value clamped below 2 is -infinity value with error "(single ints) -infinity < max" + assert 1 clamped below infinity value is 1 with error "(single ints) value < infinity" + + set {_expected::*} to (NaN value, -infinity value, and 2.0) + set {_got::*} to ({_null}, NaN value, -infinity value, infinity value) clamped below 2.0 + assert isNaN({_got::1}) is true with error "(edge cases list) NaN" + assert {_got::2} is {_expected::2} with error "(edge cases list) -infinity" + assert {_got::3} is {_expected::3} with error "(edge cases list) infinity" + + # Lower limits only (above) + + # Normal Cases + assert 1 clamped above 0 is 1 with error "(single ints) min < value" + assert 1 clamped above 1 is 1 with error "(single ints) min = value" + assert 0 clamped above 1 is 1 with error "(single ints) value < min" + + assert 1.999 clamped above 0.0 is 1.999 with error "(single floats) min < value" + assert 1.999 clamped above 1.999 is 1.999 with error "(single floats) min = value" + assert 0.0 clamped above 1.999 is 1.999 with error "(single floats) value < min" + + # Lists + set {_expected::*} to (0, 0, 1, 2, 3, and 4) + # this is dumb but comparing the lists directly didn't work + set {_got::*} to (-1, 0, 1, 2, 3, and 4) clamped above 0 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple ints)" + set {_got::*} to (-1.999, 0.0, 1.0, 2.0, 3.0, and 4.0) clamped above 0.0 + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with error "(multiple floats)" + + # Edge Cases + assert 1 clamped above {_null} is 1 with error "(single ints) min = null" + assert {_null} clamped above 1 is not set with error "(single ints) value = null" + assert isNaN(1 clamped above NaN value) is true with error "(single ints) NaN < value" + assert isNaN(NaN value clamped above 1) is true with error "(single ints) min < NaN" + assert infinity value clamped above 1 is infinity value with error "(single ints) min < infinity" + assert -infinity value clamped above 1 is 1 with error "(single ints) min < -infinity" + assert 1 clamped above -infinity value is 1 with error "(single ints) -infinity < value" + + set {_expected::*} to (NaN value, 0.0, and infinity value) + set {_got::*} to ({_null}, NaN value, -infinity value, infinity value) clamped above 0.0 + assert isNaN({_got::1}) is true with error "(edge cases list) NaN" + assert {_got::2} is {_expected::2} with error "(edge cases list) -infinity" + assert {_got::3} is {_expected::3} with error "(edge cases list) infinity"