diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/DimensionScheduler.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/DimensionScheduler.java index a02536e..e777fd3 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/DimensionScheduler.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/DimensionScheduler.java @@ -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 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 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> commands() { return List.of(new DimensionScheduleCommand()); diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/README.md b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/README.md index 8bea12c..1f91d4a 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/README.md +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/dimensionScheduler/README.md @@ -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. diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/endPrevent/EndOpeningSchedule.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/endPrevent/EndOpeningSchedule.java index 90fd372..c1fa26e 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/endPrevent/EndOpeningSchedule.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/endPrevent/EndOpeningSchedule.java @@ -26,6 +26,7 @@ final class EndOpeningSchedule { private Optional 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 endOpenAt, Optional 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 endOpenAt, Instant now) { this.handledAnnouncements.clear();