Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ee65bfb
implementation for delay Sectio
ahmadmsaleem Apr 15, 2026
f461ac0
Adding back the comments I removed :)
ahmadmsaleem Apr 15, 2026
d57002b
add section form for deferred body execution
ahmadmsaleem Apr 16, 2026
7370afb
delegate getActualNext and executionIntent
ahmadmsaleem Apr 16, 2026
f78fbfa
Address review feedback for delay section
ahmadmsaleem Apr 17, 2026
8309b29
Update src/main/java/ch/njol/skript/effects/Delay.java
ahmadmsaleem Apr 26, 2026
858453a
Update src/main/java/ch/njol/skript/effects/Delay.java
ahmadmsaleem Apr 26, 2026
1358f91
Update src/main/java/ch/njol/skript/effects/Delay.java
ahmadmsaleem Apr 26, 2026
620826b
Update src/main/java/ch/njol/skript/effects/Delay.java
ahmadmsaleem Apr 26, 2026
7096a81
Drop SkriptTimings block from Delay
ahmadmsaleem Apr 28, 2026
180b420
Merge branch 'dev/feature' into feature/delay-section
ahmadmsaleem May 5, 2026
ad16178
Merge branch 'dev/feature' into feature/delay-section
ahmadmsaleem May 5, 2026
33b6e32
Swapped to SectionUtils.loadDelayableLinkedCode
ahmadmsaleem May 5, 2026
3b12b35
Merge branch 'dev/feature' into feature/delay-section
ahmadmsaleem May 10, 2026
48931ff
another fix
ahmadmsaleem May 13, 2026
ddac714
fixed the unnecessary removeLocals, and added warning
ahmadmsaleem May 13, 2026
964ca9d
Now the warning attaches to the wait sections line
ahmadmsaleem May 13, 2026
db9b171
Warning logic fully reverted as requested
ahmadmsaleem May 13, 2026
6d1e501
Merge branch 'dev/feature' into feature/delay-section
sovdeeth May 21, 2026
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
106 changes: 70 additions & 36 deletions src/main/java/ch/njol/skript/effects/Delay.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package ch.njol.skript.effects;

import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
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.Effect;
import ch.njol.skript.lang.EffectSection;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.Literal;
import ch.njol.skript.lang.SkriptParser.ParseResult;
Expand All @@ -20,29 +21,43 @@
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

@Name("Delay")
@Description("Delays the script's execution by a given timespan. Please note that delays are not persistent, e.g. trying to create a tempban script with <code>ban player → wait 7 days → unban player</code> will not work if you restart your server anytime within these 7 days. You also have to be careful even when using small delays!")
@Description({
"Delays the script's execution by a given timespan.",
"When used as an effect, all code after the <code>wait</code> runs once the delay elapses. The whole trigger pauses.",
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
"When used as a section, only the section body is deferred. Code after the section continues immediately, and the body runs on the same event once the delay elapses. This is useful for scheduling follow-up work without blocking the rest of the trigger.",
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
"Note that delays are not persistent. For example, <code>ban player → wait 7 days → unban player</code> will not resume if the server restarts during the delay.",
"Inside a section body, outer <code>loop-value</code>s are not available because the body is parsed as a separate trigger. Event values (like <code>player</code>) still work. If you need a variable's value from before the delay, copy it to a local variable &mdash; local variables are snapshotted when the section is scheduled."
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
})
@Example("wait 2 minutes")
@Example("halt for 5 minecraft hours")
@Example("wait a tick")
@Since("1.4")
public class Delay extends Effect {
@Example("""
# Section form: outer continues immediately, body runs 3 seconds later.
send "hello" to player
wait 3 seconds:
send "...and goodbye" to player
""")
Comment thread
ahmadmsaleem marked this conversation as resolved.
@Since("1.4, INSERT VERSION (Delayed Sections)")
public class Delay extends EffectSection {

static {
Skript.registerEffect(Delay.class, "(wait|halt) [for] %timespan%");
Skript.registerSection(Delay.class, "(wait|halt) [for] %timespan%");
}

@SuppressWarnings("NotNullFieldNotInitialized")
protected Expression<Timespan> duration;

private @Nullable Trigger trigger;

@SuppressWarnings({"unchecked", "null"})
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
getParser().setHasDelayBefore(Kleenean.TRUE);

public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult,
@Nullable SectionNode sectionNode, @Nullable List<TriggerItem> triggerItems) {
duration = (Expression<Timespan>) exprs[0];
if (duration instanceof Literal) { // If we can, do sanity check for delays
Timespan timespan = ((Literal<Timespan>) duration).getSingle();
Expand All @@ -56,51 +71,70 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
}
}

if (hasSection()) {
Comment thread
ahmadmsaleem marked this conversation as resolved.
assert sectionNode != null;
// Parse the body under the outer event context so event values still resolve inside it.
// Locals are isolated at runtime via the swap dance in walk().
Class<? extends Event>[] outerEvents = getParser().getCurrentEvents();
Runnable beforeLoading = () -> getParser().setHasDelayBefore(Kleenean.TRUE);
trigger = loadCode(sectionNode, "wait", beforeLoading, null, outerEvents);
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
// Outer trigger is NOT delayed - code after the section runs immediately.
return trigger != null;
}

getParser().setHasDelayBefore(Kleenean.TRUE);
return true;
}

@Override
@Nullable
protected TriggerItem walk(Event event) {
protected @Nullable TriggerItem walk(Event event) {
debug(event, true);
long start = Skript.debug() ? System.nanoTime() : 0;
TriggerItem next = getNext();
if (next != null && Skript.getInstance().isEnabled()) { // See https://github.com/SkriptLang/Skript/issues/3702

Timespan duration = this.duration.getSingle(event);
if (duration == null)
return null;

// Back up local variables
Object localVars = Variables.removeLocals(event);


if (!Skript.getInstance().isEnabled()) // See https://github.com/SkriptLang/Skript/issues/3702
return trigger != null ? super.walk(event, false) : null;

Timespan duration = this.duration.getSingle(event);
if (duration == null)
return trigger != null ? super.walk(event, false) : null;

long ticks = Math.max(duration.getAs(Timespan.TimePeriod.TICK), 1); // Minimum delay is one tick, less than it is useless!

TriggerItem afterDelay = trigger != null ? trigger : getNext();
if (afterDelay != null) {
boolean isSection = trigger != null;
Object localVars = isSection ? Variables.copyLocalVariables(event) : Variables.removeLocals(event);

Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> {
addDelayedEvent(event);
if (!isSection)
addDelayedEvent(event);
Comment thread
ahmadmsaleem marked this conversation as resolved.
Skript.debug(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1_000_000_000. + "s");

// Re-set local variables
Object outerLocals = isSection ? Variables.removeLocals(event) : null;
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
if (localVars != null)
Variables.setLocalVariables(event, localVars);

Object timing = null; // Timings reference must be kept so that it can be stopped after TriggerItem execution
if (SkriptTimings.enabled()) { // getTrigger call is not free, do it only if we must
Trigger trigger = getTrigger();
if (trigger != null)
timing = SkriptTimings.start(trigger.getDebugLabel());
Object timing = null;
if (SkriptTimings.enabled()) {
Trigger parentTrigger = getTrigger();
if (parentTrigger != null)
timing = SkriptTimings.start(parentTrigger.getDebugLabel());
}
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated

TriggerItem.walk(next, event);
Variables.removeLocals(event); // Clean up local vars, we may be exiting now
TriggerItem.walk(afterDelay, event);

if (isSection)
Variables.setLocalVariables(event, outerLocals);
Comment thread
ahmadmsaleem marked this conversation as resolved.
Outdated
else
Variables.removeLocals(event); // Clean up local vars, we may be exiting now

SkriptTimings.stop(timing); // Stop timing if it was even started
}, Math.max(duration.getAs(Timespan.TimePeriod.TICK), 1)); // Minimum delay is one tick, less than it is useless!
SkriptTimings.stop(timing);
}, ticks);
}
return null;
}

@Override
protected void execute(Event event) {
Comment thread
ahmadmsaleem marked this conversation as resolved.
throw new UnsupportedOperationException();
if (trigger != null)
return super.walk(event, false);
return null;
}

@Override
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/ch/njol/skript/lang/EffectSectionEffect.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public TriggerItem setNext(@Nullable TriggerItem next) {
return effectSection.getNext();
}

@Override
public @Nullable TriggerItem getActualNext() {
return effectSection.getActualNext();
}

@Override
public @Nullable ExecutionIntent executionIntent() {
return effectSection.executionIntent();
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return effectSection.toString(event, debug);
Expand Down