Enhance DimensionScheduler and EndOpeningSchedule with load time tracking and state restoration logic

This commit is contained in:
besiece committed 2026-11-08 19:35:22 +01:00
1 parent 45685c0c13
commit f3f19a3370
3 files changed
+30 -9

No files matched your search

@@ -23,6 +23,7 @@ public class DimensionScheduler extends Appliance {
private static final String FILE_NAME = "dimension-schedule.yml";
private final File scheduleFile = new File(Main.instance().getDataFolder(), FILE_NAME);
private Map<String, Instant> times = Map.of();
private Instant loadedAt = Instant.now();
@Override
public void onEnable() {
@@ -44,6 +45,7 @@ public class DimensionScheduler extends Appliance {
readTime(entry.getKey(), entry.getValue()).ifPresent(time -> loadedTimes.put(entry.getKey(), time));
}
this.times = Map.copyOf(loadedTimes);
this.loadedAt = Instant.now();
}
private static Optional<Instant> readTime(String key, Object configured) {
@@ -68,6 +70,11 @@ public class DimensionScheduler extends Appliance {
return this.times;
}
/** Wall-clock time of the last successful load, before consumers check the new times. */
public Instant getLoadedAt() {
return this.loadedAt;
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new DimensionScheduleCommand());
@@ -60,6 +60,11 @@ setzen die bereits behandelten Stufen nicht zurück. Bei Verzögerungen wird hö
zuletzt fällige Vorankündigung ausgegeben. Ist die Öffnung bereits fällig, entfallen alte Teaser.
Während einer manuellen Freigabe fällige Teaser werden still als behandelt markiert.
Für einen geänderten Termin gilt der erfolgreiche Ladezeitpunkt als Grenze für verpasste
Stufen. Wird eine Ankündigung erst zwischen dem Reload und der nächsten Sekundenprüfung
fällig, wird sie regulär ausgeführt. Dasselbe gilt für eine in diesem Zeitraum fällige Öffnung.
Beim erneuten Aktivieren von EndPrevent gilt frühestens dessen Startzeitpunkt als Grenze.
Ein Rechtsklick mit Enderauge auf einen leeren, gesperrten Endportalrahmen wird für beide Hände
abgebrochen. Nur der auslösende Spieler erhält einen der fünf zufälligen Texte in der Actionbar,
höchstens einmal pro Server-Tick. Die Spieler-Zuordnung wird beim Verlassen entfernt.
@@ -26,6 +26,7 @@ final class EndOpeningSchedule {
private Optional<Instant> previousOuterEndOpenAt = Optional.empty();
private @Nullable EndPrevent.State previousScheduledState;
private boolean initialized;
private Instant startedAt = Instant.EPOCH;
EndOpeningSchedule(EndPrevent endPrevent, DimensionScheduler scheduler, EndOpeningEffects effects) {
this.endPrevent = endPrevent;
@@ -35,6 +36,7 @@ final class EndOpeningSchedule {
void start() {
this.stop();
this.startedAt = Instant.now();
// Wait until the next tick so all appliances have finished loading, regardless of order.
this.task = Bukkit.getScheduler().runTaskTimer(Main.instance(), this::checkOpeningTimes, 1L, 20L);
}
@@ -52,7 +54,9 @@ final class EndOpeningSchedule {
boolean endTimeChanged = !endOpenAt.equals(this.previousEndOpenAt);
boolean restoreSchedule = !this.initialized || endTimeChanged
|| !outerEndOpenAt.equals(this.previousOuterEndOpenAt);
if(!this.initialized || endTimeChanged) this.resetAnnouncements(endOpenAt, now);
Instant restoreAt = this.scheduler.getLoadedAt();
if(!this.initialized && this.startedAt.isAfter(restoreAt)) restoreAt = this.startedAt;
if(!this.initialized || endTimeChanged) this.resetAnnouncements(endOpenAt, restoreAt);
this.previousEndOpenAt = endOpenAt;
this.previousOuterEndOpenAt = outerEndOpenAt;
this.initialized = true;
@@ -62,20 +66,19 @@ final class EndOpeningSchedule {
return;
}
EndPrevent.State scheduledState;
if(endOpenAt.isPresent() && now.isBefore(endOpenAt.get())) {
scheduledState = EndPrevent.State.CLOSED;
} else if(outerEndOpenAt.isPresent() && now.isBefore(outerEndOpenAt.get())) {
scheduledState = EndPrevent.State.NO_OUTER;
} else {
scheduledState = EndPrevent.State.OPEN;
EndPrevent.State scheduledState = stateAt(now, endOpenAt, outerEndOpenAt);
EndPrevent.State restoredState = stateAt(restoreAt, endOpenAt, outerEndOpenAt);
boolean crossedAfterLoad = restoreSchedule && restoredState != scheduledState;
if(crossedAfterLoad) {
// Restore the state at load time silently, then execute the newly due live transition.
this.endPrevent.restoreEndState(restoredState);
}
// Remember the schedule's state, not the current manual state: commands remain effective
// until the next scheduled transition or a change to the relevant opening times.
if(restoreSchedule || scheduledState != this.previousScheduledState) {
if(this.endPrevent.getEndPreventState() != scheduledState) {
if(restoreSchedule) {
if(restoreSchedule && !crossedAfterLoad) {
this.endPrevent.restoreEndState(scheduledState);
} else {
this.endPrevent.setEndState(scheduledState);
@@ -89,6 +92,12 @@ final class EndOpeningSchedule {
this.announceDueStages(endOpenAt, now);
}
private static EndPrevent.State stateAt(Instant at, Optional<Instant> endOpenAt, Optional<Instant> outerEndOpenAt) {
if(endOpenAt.isPresent() && at.isBefore(endOpenAt.get())) return EndPrevent.State.CLOSED;
if(outerEndOpenAt.isPresent() && at.isBefore(outerEndOpenAt.get())) return EndPrevent.State.NO_OUTER;
return EndPrevent.State.OPEN;
}
/** A new opening time starts a new cycle; announcements already in the past are consumed. */
private void resetAnnouncements(Optional<Instant> endOpenAt, Instant now) {
this.handledAnnouncements.clear();