From bedea0d68e483cd417e52583e33e4d263e57ae07 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:17:49 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Add=20`loop-counter`=20&=20Impro?= =?UTF-8?q?ve=20loops=20(#4595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/effects/EffContinue.java | 48 ++++---- .../java/ch/njol/skript/effects/EffExit.java | 72 ++++++----- .../ch/njol/skript/effects/EffReturn.java | 56 ++++----- .../skript/expressions/ExprLoopIteration.java | 115 ++++++++++++++++++ .../skript/expressions/ExprLoopValue.java | 55 +++++---- .../java/ch/njol/skript/lang/LoopSection.java | 57 +++++++++ .../java/ch/njol/skript/sections/SecLoop.java | 36 +++--- .../ch/njol/skript/sections/SecWhile.java | 32 ++--- .../tests/regressions/4595-loop-iteration.sk | 28 +++++ 9 files changed, 359 insertions(+), 140 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java create mode 100644 src/main/java/ch/njol/skript/lang/LoopSection.java create mode 100644 src/test/skript/tests/regressions/4595-loop-iteration.sk diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 263ffb39059..026ca015d86 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -25,25 +25,33 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.TriggerSection; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; -import java.util.stream.Collectors; @Name("Continue") -@Description("Skips the value currently being looped, moving on to the next value if it exists.") -@Examples({"loop all players:", +@Description("Immediately moves the (while) loop on to the next iteration.") +@Examples({ + "# Broadcast online moderators", + "loop all players:", "\tif loop-value does not have permission \"moderator\":", - "\t\tcontinue # filter out non moderators", - "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast"}) + "\t\tcontinue # filter out non moderators", + "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast", + " ", + "# Game starting counter", + "set {_counter} to 11", + "while {_counter} > 0:", + "\tremove 1 from {_counter}", + "\twait a second", + "\tif {_counter} != 1, 2, 3, 5 or 10:", + "\t\tcontinue # only print when counter is 1, 2, 3, 5 or 10", + "\tbroadcast \"Game starting in %{_counter}% second(s)\"", +}) @Since("2.2-dev37, 2.7 (while loops)") public class EffContinue extends Effect { @@ -52,36 +60,34 @@ public class EffContinue extends Effect { } @SuppressWarnings("NotNullFieldNotInitialized") - private TriggerSection section; + private LoopSection loop; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - List currentSections = ParserInstance.get().getCurrentSections().stream() - .filter(s -> s instanceof SecLoop || s instanceof SecWhile) - .collect(Collectors.toList()); + List currentLoops = getParser().getCurrentSections(LoopSection.class); - if (currentSections.isEmpty()) { - Skript.error("Continue may only be used in while or loops"); + if (currentLoops.isEmpty()) { + Skript.error("The 'continue' effect may only be used in while and regular loops"); return false; } - section = currentSections.get(currentSections.size() - 1); + loop = currentLoops.get(currentLoops.size() - 1); return true; } @Override - protected void execute(Event e) { + protected void execute(Event event) { throw new UnsupportedOperationException(); } - @Nullable @Override - protected TriggerItem walk(Event e) { - return section; + @Nullable + protected TriggerItem walk(Event event) { + return loop; } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "continue"; } diff --git a/src/main/java/ch/njol/skript/effects/EffExit.java b/src/main/java/ch/njol/skript/effects/EffExit.java index 64f7fab4cdb..eecbb7efec1 100644 --- a/src/main/java/ch/njol/skript/effects/EffExit.java +++ b/src/main/java/ch/njol/skript/effects/EffExit.java @@ -25,50 +25,51 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.sections.SecConditional; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; -/** - * @author Peter Güttinger - */ @Name("Exit") @Description("Exits a given amount of loops and conditionals, or the entire trigger.") -@Examples({"if player has any ore:", - " stop", - "message \"%player% has no ores!\"", - "loop blocks above the player:", - " loop-block is not air:", - " exit 2 sections", - " set loop-block to water"}) +@Examples({ + "if player has any ore:", + "\tstop", + "message \"%player% has no ores!\"", + "loop blocks above the player:", + "\tloop-block is not air:", + "\t\texit 2 sections", + "\tset loop-block to water" +}) @Since("unknown (before 2.1)") public class EffExit extends Effect { // TODO [code style] warn user about code after a stop effect + static { Skript.registerEffect(EffExit.class, "(exit|stop) [trigger]", - "(exit|stop) [(1|a|the|this)] (0¦section|1¦loop|2¦conditional)", - "(exit|stop) <\\d+> (0¦section|1¦loop|2¦conditional)s", - "(exit|stop) all (0¦section|1¦loop|2¦conditional)s"); + "(exit|stop) [(1|a|the|this)] (section|1:loop|2:conditional)", + "(exit|stop) <\\d+> (section|1:loop|2:conditional)s", + "(exit|stop) all (section|1:loop|2:conditional)s"); } private int breakLevels; - private final static int EVERYTHING = 0, LOOPS = 1, CONDITIONALS = 2; - private final static String[] names = {"sections", "loops", "conditionals"}; + private static final int EVERYTHING = 0; + private static final int LOOPS = 1; + private static final int CONDITIONALS = 2; + private static final String[] names = {"sections", "loops", "conditionals"}; private int type; @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { switch (matchedPattern) { case 0: breakLevels = getParser().getCurrentSections().size() + 1; @@ -102,44 +103,41 @@ private static int numLevels(int type) { List currentSections = ParserInstance.get().getCurrentSections(); if (type == EVERYTHING) return currentSections.size(); - int r = 0; - for (TriggerSection s : currentSections) { - if (type == CONDITIONALS ? s instanceof SecConditional : s instanceof SecLoop || s instanceof SecWhile) - r++; + int level = 0; + for (TriggerSection section : currentSections) { + if (type == CONDITIONALS ? section instanceof SecConditional : section instanceof LoopSection) + level++; } - return r; + return level; } @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, false); - TriggerItem n = this; + protected TriggerItem walk(Event event) { + debug(event, false); + TriggerItem node = this; for (int i = breakLevels; i > 0;) { - n = n.getParent(); - if (n == null) { + node = node.getParent(); + if (node == null) { assert false : this; return null; } - if (n instanceof SecLoop) { - ((SecLoop) n).exit(e); - } else if (n instanceof SecWhile) { - ((SecWhile) n).reset(); - } + if (node instanceof LoopSection) + ((LoopSection) node).exit(event); - if (type == EVERYTHING || type == CONDITIONALS && n instanceof SecConditional || type == LOOPS && (n instanceof SecLoop || n instanceof SecWhile)) + if (type == EVERYTHING || type == CONDITIONALS && node instanceof SecConditional || type == LOOPS && (node instanceof LoopSection)) i--; } - return n instanceof SecLoop ? ((SecLoop) n).getActualNext() : n instanceof SecWhile ? ((SecWhile) n).getActualNext() : n.getNext(); + return node instanceof LoopSection ? ((LoopSection) node).getActualNext() : node.getNext(); } @Override - protected void execute(final Event e) { + protected void execute(Event event) { assert false; } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "stop " + breakLevels + " " + names[type]; } diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 20cf0bdfb0e..2240c509767 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -26,6 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; @@ -34,19 +35,16 @@ import ch.njol.skript.lang.function.ScriptFunction; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Return") @Description("Makes a function return a value") -@Examples({"function double(i: number) :: number:", - " return 2 * {_i}"}) +@Examples({ + "function double(i: number) :: number:", + "\treturn 2 * {_i}" +}) @Since("2.2") public class EffReturn extends Effect { @@ -75,18 +73,18 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } function = f; - ClassInfo rt = function.getReturnType(); - if (rt == null) { + ClassInfo returnType = function.getReturnType(); + if (returnType == null) { Skript.error("This function doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function."); return false; } RetainingLogHandler log = SkriptLogger.startRetainingLog(); - Expression v; + Expression convertedExpr; try { - v = exprs[0].getConvertedExpression(rt.getC()); - if (v == null) { - log.printErrors("This function is declared to return " + rt.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type."); + convertedExpr = exprs[0].getConvertedExpression(returnType.getC()); + if (convertedExpr == null) { + log.printErrors("This function is declared to return " + returnType.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type."); return false; } log.printLog(); @@ -94,33 +92,31 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye log.stop(); } - if (f.isSingle() && !v.isSingle()) { - Skript.error("This function is defined to only return a single " + rt.toString() + ", but this return statement can return multiple values."); + if (f.isSingle() && !convertedExpr.isSingle()) { + Skript.error("This function is defined to only return a single " + returnType.toString() + ", but this return statement can return multiple values."); return false; } - value = v; + value = convertedExpr; return true; } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, false); - if (e instanceof FunctionEvent) { - ((ScriptFunction) function).setReturnValue(value.getArray(e)); + @SuppressWarnings({"unchecked", "rawtypes"}) + protected TriggerItem walk(Event event) { + debug(event, false); + if (event instanceof FunctionEvent) { + ((ScriptFunction) function).setReturnValue(value.getArray(event)); } else { - assert false : e; + assert false : event; } TriggerSection parent = getParent(); while (parent != null) { - if (parent instanceof SecLoop) { - ((SecLoop) parent).exit(e); - } else if (parent instanceof SecWhile) { - ((SecWhile) parent).reset(); - } + if (parent instanceof LoopSection) + ((LoopSection) parent).exit(event); + parent = parent.getParent(); } @@ -128,13 +124,13 @@ protected TriggerItem walk(final Event e) { } @Override - protected void execute(Event e) { + protected void execute(Event event) { assert false; } @Override - public String toString(@Nullable Event e, boolean debug) { - return "return " + value.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "return " + value.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java new file mode 100644 index 00000000000..b95471a2cba --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java @@ -0,0 +1,115 @@ +/** + * 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.Literal; +import ch.njol.skript.lang.LoopSection; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Loop Iteration") +@Description("Returns the loop's current iteration count (for both normal and while loops).") +@Examples({ + "while player is online:", + "\tgive player 1 stone", + "\twait 5 ticks", + "\tif loop-counter > 30:", + "\t\tstop loop", + "", + "loop {top-balances::*}:", + "\tif loop-iteration <= 10:", + "\t\tbroadcast \"##%loop-iteration% %loop-index% has $%loop-value%\"", +}) +@Since("INSERT VERSION") +public class ExprLoopIteration extends SimpleExpression { + + static { + Skript.registerExpression(ExprLoopIteration.class, Long.class, ExpressionType.SIMPLE, "[the] loop(-| )(counter|iteration)[-%-*number%]"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private LoopSection loop; + + private int loopNumber; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + loopNumber = -1; + if (exprs[0] != null) + loopNumber = ((Literal) exprs[0]).getSingle().intValue(); + + int i = 1; + LoopSection loop = null; + + for (LoopSection l : getParser().getCurrentSections(LoopSection.class)) { + if (i < loopNumber) { + i++; + continue; + } + if (loop != null) { + Skript.error("There are multiple loops. Use loop-iteration-1/2/3/etc. to specify which loop-iteration you want."); + return false; + } + loop = l; + if (i == loopNumber) + break; + } + + if (loop == null) { + Skript.error("The loop iteration expression must be used in a loop"); + return false; + } + + this.loop = loop; + return true; + } + + @Override + protected Long[] get(Event event) { + return new Long[]{loop.getLoopCounter(event)}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Long.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "loop-iteration" + (loopNumber != -1 ? ("-" + loopNumber) : ""); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 4ef78dd68c6..5767d6f3352 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -31,7 +31,6 @@ import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.sections.SecLoop; @@ -47,60 +46,69 @@ /** * Used to access a loop's current value. - *

- * TODO expression to get the current # of execution (e.g. loop-index/number/count/etc (not number though)); - * - * @author Peter Güttinger */ @Name("Loop value") -@Description("The currently looped value.") -@Examples({"# countdown:", - "loop 10 times:", - " message \"%11 - loop-number%\"", - " wait a second", - "# generate a 10x10 floor made of randomly colored wool below the player:", - "loop blocks from the block below the player to the block 10 east of the block below the player:", - " loop blocks from the loop-block to the block 10 north of the loop-block:", - " set loop-block-2 to any wool"}) -@Since("1.0") +@Description("Returns the currently looped value.") +@Examples({ + "# Countdown", + "loop 10 times:", + "\tmessage \"%11 - loop-number%\"", + "\twait a second", + "", + "# Generate a 10x10 floor made of randomly colored wool below the player", + "loop blocks from the block below the player to the block 10 east of the block below the player:", + "\tloop blocks from the loop-block to the block 10 north of the loop-block:", + "\t\tset loop-block-2 to any wool", + "", + "loop {top-balances::*}:", + "\tloop-iteration <= 10", + "\tsend \"##%loop-iteration% %loop-index% has $%loop-value%\"", +}) +@Since("1.0, INSERT VERSION (loop-counter)") public class ExprLoopValue extends SimpleExpression { static { Skript.registerExpression(ExprLoopValue.class, Object.class, ExpressionType.SIMPLE, "[the] loop-<.+>"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private String name; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private SecLoop loop; // whether this loops a variable boolean isVariableLoop = false; // if this loops a variable and isIndex is true, return the index of the variable instead of the value boolean isIndex = false; - + + private static final Pattern LOOP_PATTERN = Pattern.compile("^(.+)-(\\d+)$"); + @Override public boolean init(Expression[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parser) { name = parser.expr; String s = "" + parser.regexes.get(0).group(); int i = -1; - Matcher m = Pattern.compile("^(.+)-(\\d+)$").matcher(s); + Matcher m = LOOP_PATTERN.matcher(s); if (m.matches()) { s = "" + m.group(1); i = Utils.parseInt("" + m.group(2)); } + + if ("counter".equalsIgnoreCase(s) || "iteration".equalsIgnoreCase(s)) // ExprLoopIteration - in case of classinfo conflicts + return false; + Class c = Classes.getClassFromUserInput(s); int j = 1; SecLoop loop = null; for (SecLoop l : getParser().getCurrentSections(SecLoop.class)) { - if ((c != null && c.isAssignableFrom(l.getLoopedExpression().getReturnType())) || "value".equals(s) || l.getLoopedExpression().isLoopOf(s)) { + if ((c != null && c.isAssignableFrom(l.getLoopedExpression().getReturnType())) || "value".equalsIgnoreCase(s) || l.getLoopedExpression().isLoopOf(s)) { if (j < i) { j++; continue; } if (loop != null) { - Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want.", ErrorQuality.SEMANTIC_ERROR); + Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want."); return false; } loop = l; @@ -109,7 +117,7 @@ public boolean init(Expression[] vars, int matchedPattern, Kleenean isDelayed } } if (loop == null) { - Skript.error("There's no loop that matches 'loop-" + s + "'", ErrorQuality.SEMANTIC_ERROR); + Skript.error("There's no loop that matches 'loop-" + s + "'"); return false; } if (loop.getLoopedExpression() instanceof Variable) { @@ -126,9 +134,9 @@ public boolean isSingle() { return true; } - @SuppressWarnings("unchecked") @Override @Nullable + @SuppressWarnings("unchecked") protected ConvertedExpression getConvertedExpr(Class... to) { if (isVariableLoop && !isIndex) { Class superType = (Class) Utils.getSuperType(to); @@ -165,6 +173,7 @@ protected Object[] get(Event e) { one[0] = current.getValue(); return one; } + Object[] one = (Object[]) Array.newInstance(getReturnType(), 1); one[0] = loop.getCurrent(e); return one; diff --git a/src/main/java/ch/njol/skript/lang/LoopSection.java b/src/main/java/ch/njol/skript/lang/LoopSection.java new file mode 100644 index 00000000000..f733ad2eaaf --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/LoopSection.java @@ -0,0 +1,57 @@ +/** + * 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.lang; + +import org.bukkit.event.Event; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Represents a loop section. + * + * @see ch.njol.skript.sections.SecWhile + * @see ch.njol.skript.sections.SecLoop + */ +public abstract class LoopSection extends Section implements SyntaxElement, Debuggable { + + protected final transient Map currentLoopCounter = new WeakHashMap<>(); + + /** + * @param event The event where the loop is used to return its loop iterations + * @return The loop iteration number + */ + public long getLoopCounter(Event event) { + return currentLoopCounter.getOrDefault(event, 1L); + } + + /** + * @return The next {@link TriggerItem} after the loop + */ + public abstract TriggerItem getActualNext(); + + /** + * Exit the loop, used to reset the loop properties such as iterations counter + * @param event The event where the loop is used to reset its relevant properties + */ + public void exit(Event event) { + currentLoopCounter.remove(event); + } + +} diff --git a/src/main/java/ch/njol/skript/sections/SecLoop.java b/src/main/java/ch/njol/skript/sections/SecLoop.java index 1b3826f81ce..8f0682d2bea 100644 --- a/src/main/java/ch/njol/skript/sections/SecLoop.java +++ b/src/main/java/ch/njol/skript/sections/SecLoop.java @@ -26,7 +26,7 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.Variable; @@ -80,7 +80,7 @@ "the player and loop-value is the actually coins value such as 200" }) @Since("1.0") -public class SecLoop extends Section { +public class SecLoop extends LoopSection { static { Skript.registerSection(SecLoop.class, "loop %objects%"); @@ -96,6 +96,7 @@ public class SecLoop extends Section { private TriggerItem actualNext; @Override + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, @@ -116,7 +117,7 @@ public boolean init(Expression[] exprs, } if (expr.isSingle()) { - Skript.error("Can't loop " + expr + " because it's only a single value"); + Skript.error("Can't loop '" + expr + "' because it's only a single value"); return false; } @@ -128,35 +129,36 @@ public boolean init(Expression[] exprs, @Override @Nullable - protected TriggerItem walk(Event e) { - Iterator iter = currentIter.get(e); + protected TriggerItem walk(Event event) { + Iterator iter = currentIter.get(event); if (iter == null) { - iter = expr instanceof Variable ? ((Variable) expr).variablesIterator(e) : expr.iterator(e); + iter = expr instanceof Variable ? ((Variable) expr).variablesIterator(event) : expr.iterator(event); if (iter != null) { if (iter.hasNext()) - currentIter.put(e, iter); + currentIter.put(event, iter); else iter = null; } } if (iter == null || !iter.hasNext()) { - exit(e); - debug(e, false); + exit(event); + debug(event, false); return actualNext; } else { - current.put(e, iter.next()); - return walk(e, true); + current.put(event, iter.next()); + currentLoopCounter.put(event, (currentLoopCounter.getOrDefault(event, 0L)) + 1); + return walk(event, true); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "loop " + expr.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "loop " + expr.toString(event, debug); } @Nullable - public Object getCurrent(Event e) { - return current.get(e); + public Object getCurrent(Event event) { + return current.get(event); } public Expression getLoopedExpression() { @@ -170,12 +172,16 @@ public SecLoop setNext(@Nullable TriggerItem next) { } @Nullable + @Override public TriggerItem getActualNext() { return actualNext; } + @Override public void exit(Event event) { current.remove(event); currentIter.remove(event); + super.exit(event); } + } diff --git a/src/main/java/ch/njol/skript/sections/SecWhile.java b/src/main/java/ch/njol/skript/sections/SecWhile.java index 102d6b620f3..18b7c42634a 100644 --- a/src/main/java/ch/njol/skript/sections/SecWhile.java +++ b/src/main/java/ch/njol/skript/sections/SecWhile.java @@ -26,7 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.util.Kleenean; @@ -34,6 +34,8 @@ import org.eclipse.jdt.annotation.Nullable; import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; @Name("While Loop") @Description("While Loop sections are loops that will just keep repeating as long as a condition is met.") @@ -54,10 +56,10 @@ "\twait 1 second # without using a delay effect the server will crash", }) @Since("2.0, 2.6 (do while)") -public class SecWhile extends Section { +public class SecWhile extends LoopSection { static { - Skript.registerSection(SecWhile.class, "[(1¦do)] while <.+>"); + Skript.registerSection(SecWhile.class, "[(:do)] while <.+>"); } @SuppressWarnings("NotNullFieldNotInitialized") @@ -81,23 +83,23 @@ public boolean init(Expression[] exprs, condition = Condition.parse(expr, "Can't understand this condition: " + expr); if (condition == null) return false; - doWhile = parseResult.mark == 1; - loadOptionalCode(sectionNode); + doWhile = parseResult.hasTag("do"); + loadOptionalCode(sectionNode); super.setNext(this); - return true; } @Nullable @Override - protected TriggerItem walk(Event e) { - if ((doWhile && !ranDoWhile) || condition.check(e)) { + protected TriggerItem walk(Event event) { + if ((doWhile && !ranDoWhile) || condition.check(event)) { ranDoWhile = true; - return walk(e, true); + currentLoopCounter.put(event, (currentLoopCounter.getOrDefault(event, 0L)) + 1); + return walk(event, true); } else { - reset(); - debug(e, false); + exit(event); + debug(event, false); return actualNext; } } @@ -114,12 +116,14 @@ public TriggerItem getActualNext() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return (doWhile ? "do " : "") + "while " + condition.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return (doWhile ? "do " : "") + "while " + condition.toString(event, debug); } - public void reset() { + @Override + public void exit(Event event) { ranDoWhile = false; + super.exit(event); } } diff --git a/src/test/skript/tests/regressions/4595-loop-iteration.sk b/src/test/skript/tests/regressions/4595-loop-iteration.sk new file mode 100644 index 00000000000..cca967abf79 --- /dev/null +++ b/src/test/skript/tests/regressions/4595-loop-iteration.sk @@ -0,0 +1,28 @@ +test "loop-iteration": + + # Test without variables + loop 2 times: + add loop-iteration to {_1} + loop 2 times: + add loop-iteration-2 to {_2} + loop 2 times: + add loop-iteration-3 to {_3} + + assert {_1} = 3 with "loop-iteration-1 not equal to 3 (value: %{_1}%)" + assert {_2} = 6 with "loop-iteration-2 not equal to 6 (value: %{_2}%)" + assert {_3} = 12 with "loop-iteration-3 not equal to 12 (value: %{_3}%)" + + delete {_1}, {_2} and {_3} # reset + + # Test with variables + add "a" and "b" to {_a::*} and {_b::*} and {_c::*} + loop {_a::*}: + add loop-iteration to {_1} + loop {_b::*}: + add loop-iteration-2 to {_2} + loop {_c::*}: + add loop-iteration-3 to {_3} + + assert {_1} = 3 with "loop-iteration-1 not equal to 3 (variables) (value: %{_1}%)" + assert {_2} = 6 with "loop-iteration-2 not equal to 6 (variables) (value: %{_2}%)" + assert {_3} = 12 with "loop-iteration-3 not equal to 12 (variables) (value: %{_3}%)"