Skip to content

Commit 69eb15f

Browse files
authored
Fix EvtAtTime not triggering when world time has been changed. (#6463)
1 parent a548538 commit 69eb15f

File tree

1 file changed

+47
-40
lines changed

1 file changed

+47
-40
lines changed

src/main/java/ch/njol/skript/events/EvtAtTime.java

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@
3232
import org.bukkit.event.Event;
3333
import org.eclipse.jdt.annotation.Nullable;
3434

35-
import java.util.ArrayList;
36-
import java.util.Collections;
3735
import java.util.Iterator;
38-
import java.util.List;
3936
import java.util.Map;
4037
import java.util.Map.Entry;
38+
import java.util.PriorityQueue;
4139
import java.util.concurrent.ConcurrentHashMap;
4240

4341
public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> {
@@ -54,20 +52,27 @@ public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> {
5452
private static final Map<World, EvtAtInfo> TRIGGERS = new ConcurrentHashMap<>();
5553

5654
private static final class EvtAtInfo {
57-
private int lastTick; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked
58-
private int currentIndex;
59-
private final List<EvtAtTime> instances = new ArrayList<>();
55+
/**
56+
* Stores the last world time that this object's instances were checked.
57+
*/
58+
private int lastCheckedTime;
59+
60+
/**
61+
* A list of all {@link EvtAtTime}s in the world this info object is responsible for.
62+
* Sorted by the time they're listening for in increasing order.
63+
*/
64+
private final PriorityQueue<EvtAtTime> instances = new PriorityQueue<>(EvtAtTime::compareTo);
6065
}
6166

62-
private int tick;
67+
private int time;
6368

6469
@SuppressWarnings("NotNullFieldNotInitialized")
6570
private World[] worlds;
6671

6772
@Override
6873
@SuppressWarnings("unchecked")
6974
public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) {
70-
tick = ((Literal<Time>) args[0]).getSingle().getTicks();
75+
time = ((Literal<Time>) args[0]).getSingle().getTicks();
7176
worlds = args[1] == null ? Bukkit.getWorlds().toArray(new World[0]) : ((Literal<World>) args[1]).getAll();
7277
return true;
7378
}
@@ -78,10 +83,9 @@ public boolean postLoad() {
7883
EvtAtInfo info = TRIGGERS.get(world);
7984
if (info == null) {
8085
TRIGGERS.put(world, info = new EvtAtInfo());
81-
info.lastTick = (int) world.getTime() - 1;
86+
info.lastCheckedTime = (int) world.getTime() - 1;
8287
}
8388
info.instances.add(this);
84-
Collections.sort(info.instances);
8589
}
8690
registerListener();
8791
return true;
@@ -93,13 +97,11 @@ public void unload() {
9397
while (iterator.hasNext()) {
9498
EvtAtInfo info = iterator.next();
9599
info.instances.remove(this);
96-
if (info.currentIndex >= info.instances.size())
97-
info.currentIndex--;
98100
if (info.instances.isEmpty())
99101
iterator.remove();
100102
}
101103

102-
if (taskID == -1 && TRIGGERS.isEmpty()) { // Unregister Bukkit listener if possible
104+
if (taskID != -1 && TRIGGERS.isEmpty()) { // Unregister Bukkit listener if possible
103105
Bukkit.getScheduler().cancelTask(taskID);
104106
taskID = -1;
105107
}
@@ -120,59 +122,64 @@ public boolean isEventPrioritySupported() {
120122
private static void registerListener() {
121123
if (taskID != -1)
122124
return;
125+
// For each world:
126+
// check each instance in order until triggerTime > (worldTime + period)
123127
taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), () -> {
124128
for (Entry<World, EvtAtInfo> entry : TRIGGERS.entrySet()) {
125129
EvtAtInfo info = entry.getValue();
126-
int tick = (int) entry.getKey().getTime();
130+
int worldTime = (int) entry.getKey().getTime();
127131

128132
// Stupid Bukkit scheduler
129-
if (info.lastTick == tick)
133+
// TODO: is this really necessary?
134+
if (info.lastCheckedTime == worldTime)
130135
continue;
131136

132137
// Check if time changed, e.g. by a command or plugin
133-
if (info.lastTick + CHECK_PERIOD * 2 < tick || info.lastTick > tick && info.lastTick - 24000 + CHECK_PERIOD * 2 < tick)
134-
info.lastTick = Math2.mod(tick - CHECK_PERIOD, 24000);
138+
// if the info was last checked more than 2 cycles ago
139+
// then reset the last checked time to the period just before now.
140+
if (info.lastCheckedTime + CHECK_PERIOD * 2 < worldTime || (info.lastCheckedTime > worldTime && info.lastCheckedTime - 24000 + CHECK_PERIOD * 2 < worldTime))
141+
info.lastCheckedTime = Math2.mod(worldTime - CHECK_PERIOD, 24000);
135142

136-
boolean midnight = info.lastTick > tick; // actually 6:00
143+
// if we rolled over from 23999 to 0, subtract 24000 from last checked
144+
boolean midnight = info.lastCheckedTime > worldTime; // actually 6:00
137145
if (midnight)
138-
info.lastTick -= 24000;
146+
info.lastCheckedTime -= 24000;
139147

140-
int startIndex = info.currentIndex;
141-
while (true) {
142-
EvtAtTime next = info.instances.get(info.currentIndex);
143-
int nextTick = midnight && next.tick > 12000 ? next.tick - 24000 : next.tick;
148+
// loop instances from earliest to latest
149+
for (EvtAtTime event : info.instances) {
150+
// if we just rolled over, the last checked time will be x - 24000, so we need to do the same to the event time
151+
int eventTime = midnight && event.time > 12000 ? event.time - 24000 : event.time;
144152

145-
if (!(info.lastTick < nextTick && nextTick <= tick))
153+
// if the event time is in the future, we don't need to check any more events.
154+
if (eventTime > worldTime)
146155
break;
147156

148-
// Execute our event
149-
ScheduledEvent event = new ScheduledEvent(entry.getKey());
150-
SkriptEventHandler.logEventStart(event);
151-
SkriptEventHandler.logTriggerEnd(next.trigger);
152-
next.trigger.execute(event);
153-
SkriptEventHandler.logTriggerEnd(next.trigger);
157+
// if we should have already caught this time previously, check the next one
158+
if (eventTime <= info.lastCheckedTime)
159+
continue;
160+
161+
// anything that makes it here must satisfy lastCheckedTime < eventTime <= worldTime
162+
// and therefore should trigger this event.
163+
ScheduledEvent scheduledEvent = new ScheduledEvent(entry.getKey());
164+
SkriptEventHandler.logEventStart(scheduledEvent);
165+
SkriptEventHandler.logTriggerEnd(event.trigger);
166+
event.trigger.execute(scheduledEvent);
167+
SkriptEventHandler.logTriggerEnd(event.trigger);
154168
SkriptEventHandler.logEventEnd();
155-
156-
info.currentIndex++;
157-
if (info.currentIndex == info.instances.size())
158-
info.currentIndex = 0;
159-
if (info.currentIndex == startIndex) // All events executed at once
160-
break;
161169
}
162-
163-
info.lastTick = tick;
170+
info.lastCheckedTime = worldTime;
164171
}
165172
}, 0, CHECK_PERIOD);
166173
}
167174

168175
@Override
169176
public String toString(@Nullable Event event, boolean debug) {
170-
return "at " + Time.toString(tick) + " in worlds " + Classes.toString(worlds, true);
177+
return "at " + Time.toString(time) + " in worlds " + Classes.toString(worlds, true);
171178
}
172179

173180
@Override
174181
public int compareTo(@Nullable EvtAtTime event) {
175-
return event == null ? tick : tick - event.tick;
182+
return event == null ? time : time - event.time;
176183
}
177184

178185
}

0 commit comments

Comments
 (0)