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 extends Number> 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"