Implement EndOpeningSchedule for managing End dimension access timing
This commit is contained in:
1 parent
3b43b594f4
commit
02d396db68
4 files changed
+150
-1
No files matched your search
+44
@@ -0,0 +1,44 @@
|
||||
# Dimension schedule
|
||||
|
||||
## Zuständigkeiten und Lebenszyklus
|
||||
|
||||
- `DimensionScheduler` wird als Appliance automatisch geladen. `onEnable()` legt
|
||||
`plugins/spawn/dimension-schedule.yml` nur dann aus der mitgelieferten Vorlage an,
|
||||
wenn die Datei fehlt, und liest alle benannten Zeitpunkte ein. Danach wird sie nur gelesen.
|
||||
- `DimensionScheduleCommand` stellt `/dimensionSchedule reload` für Konsole und OPs bereit.
|
||||
Ein fehlerhafter Reload behält alle bisherigen Zeitpunkte.
|
||||
- `EndPrevent.onEnable()` erstellt eine `EndOpeningSchedule`, sofern die
|
||||
`DimensionScheduler`-Appliance vorhanden ist. `EndPrevent.onDisable()` stoppt sie.
|
||||
- `EndOpeningSchedule` übernimmt die End-Freigabe. Ihr einziger Bukkit-Task startet
|
||||
im nächsten Tick, nachdem alle Appliances initialisiert wurden, und prüft danach alle
|
||||
20 Ticks (bei normaler Tickrate einmal pro Sekunde) die echte Uhrzeit. Die Prüfung liest
|
||||
nur die geladenen Zeitpunkte im Arbeitsspeicher und läuft auf dem Server-Hauptthread.
|
||||
|
||||
## Öffnungszeiten
|
||||
|
||||
```yaml
|
||||
end-open-at: "2026-11-15T18:00:00+01:00"
|
||||
outer-end-open-at: "2026-11-16T18:00:00+01:00"
|
||||
```
|
||||
|
||||
Vor `end-open-at` ist das End `CLOSED`, danach bis `outer-end-open-at` ist nur die
|
||||
Hauptinsel verfügbar (`NO_OUTER`), anschließend ist es vollständig `OPEN`.
|
||||
Ohne Outer-End-Termin öffnet `end-open-at` das gesamte End. Mit ausschließlich einem
|
||||
Outer-End-Termin ist die Hauptinsel sofort verfügbar und das Outer End öffnet zum Termin.
|
||||
Ein zukünftiger End-Termin hat Vorrang vor einem bereits vergangenen Outer-End-Termin.
|
||||
Sind beide Einträge leer oder `null`, erfolgt keine automatische Zustandsänderung.
|
||||
Weitere Namen können in der YAML ergänzt werden; nur die End-Klasse interpretiert diese zwei Namen.
|
||||
|
||||
Beim Start wird der zur aktuellen Uhrzeit passende Zustand angewandt, auch bei vergangenen
|
||||
Terminen. Geänderte Öffnungszeiten werden nach einem erfolgreichen Reload spätestens bei
|
||||
der nächsten Prüfung berücksichtigt. Entfernte Termine heben den zuletzt gesetzten Zustand
|
||||
nicht auf; die Steuerung bleibt dann vollständig manuell.
|
||||
|
||||
## Manuelle Commands
|
||||
|
||||
`/endPrevent preventEnd`, `/endPrevent allowEnd` und `/endPrevent onlyInnerEnd` bleiben bestehen.
|
||||
Sie werden nicht bei jeder Prüfung überschrieben: Erst beim nächsten zeitgesteuerten
|
||||
Zustandswechsel oder bei geänderten End-Terminen greift der Zeitplan erneut ein.
|
||||
Ein Reload mit unveränderten End-Terminen setzt einen manuellen Zustand nicht zurück.
|
||||
Automatische Änderungen gelten nur zur Laufzeit und schreiben keine Datei; manuelle
|
||||
Commands speichern den Zustand weiterhin wie bisher in `config.yml`.
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.endPrevent;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.dimensionScheduler.DimensionScheduler;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* End-specific interpretation of the named times. Created and stopped by EndPrevent.
|
||||
* DimensionScheduler only supplies configuration; this class owns the single polling task.
|
||||
*/
|
||||
final class EndOpeningSchedule {
|
||||
private final EndPrevent endPrevent;
|
||||
private final DimensionScheduler scheduler;
|
||||
private @Nullable BukkitTask task;
|
||||
private Optional<Instant> previousEndOpenAt = Optional.empty();
|
||||
private Optional<Instant> previousOuterEndOpenAt = Optional.empty();
|
||||
private @Nullable EndPrevent.State previousScheduledState;
|
||||
|
||||
EndOpeningSchedule(EndPrevent endPrevent, DimensionScheduler scheduler) {
|
||||
this.endPrevent = endPrevent;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
void start() {
|
||||
this.stop();
|
||||
// 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);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if(this.task == null) return;
|
||||
this.task.cancel();
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
private void checkOpeningTimes() {
|
||||
Optional<Instant> endOpenAt = this.scheduler.getTime("end-open-at");
|
||||
Optional<Instant> outerEndOpenAt = this.scheduler.getTime("outer-end-open-at");
|
||||
boolean timesChanged = !endOpenAt.equals(this.previousEndOpenAt)
|
||||
|| !outerEndOpenAt.equals(this.previousOuterEndOpenAt);
|
||||
this.previousEndOpenAt = endOpenAt;
|
||||
this.previousOuterEndOpenAt = outerEndOpenAt;
|
||||
|
||||
if(endOpenAt.isEmpty() && outerEndOpenAt.isEmpty()) {
|
||||
this.previousScheduledState = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Instant now = Instant.now();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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(timesChanged || scheduledState != this.previousScheduledState) {
|
||||
if(this.endPrevent.getEndPreventState() != scheduledState) {
|
||||
this.endPrevent.applyEndState(scheduledState);
|
||||
Main.logger().info("End opening schedule applied state " + scheduledState
|
||||
+ " (end-open-at=" + endOpenAt.orElse(null)
|
||||
+ ", outer-end-open-at=" + outerEndOpenAt.orElse(null) + ")");
|
||||
}
|
||||
this.previousScheduledState = scheduledState;
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -1,5 +1,7 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.endPrevent;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.dimensionScheduler.DimensionScheduler;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
|
||||
import eu.mhsl.craftattack.spawn.core.config.Configuration;
|
||||
@@ -7,12 +9,14 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EndPrevent extends Appliance {
|
||||
private final String endPreventKey = "endPrevent";
|
||||
private State endPreventState;
|
||||
private @Nullable EndOpeningSchedule openingSchedule;
|
||||
private final World endWorld = Bukkit.getWorlds().stream().filter(world -> world.getEnvironment().equals(World.Environment.THE_END)).findFirst().orElseThrow();
|
||||
|
||||
public enum State {
|
||||
@@ -27,6 +31,24 @@ public class EndPrevent extends Appliance {
|
||||
this.updateEndBorder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if(this.openingSchedule != null) this.openingSchedule.stop();
|
||||
DimensionScheduler scheduler = Main.instance().getAppliances().stream()
|
||||
.filter(DimensionScheduler.class::isInstance)
|
||||
.map(DimensionScheduler.class::cast)
|
||||
.findFirst().orElse(null);
|
||||
if(scheduler == null) return;
|
||||
|
||||
this.openingSchedule = new EndOpeningSchedule(this, scheduler);
|
||||
this.openingSchedule.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if(this.openingSchedule != null) this.openingSchedule.stop();
|
||||
}
|
||||
|
||||
private void updateEndBorder() {
|
||||
if(this.endPreventState == State.NO_OUTER) this.endWorld.getWorldBorder().setSize(500);
|
||||
if(this.endPreventState == State.OPEN) this.endWorld.getWorldBorder().setSize(this.endWorld.getWorldBorder().getMaxSize());
|
||||
@@ -35,6 +57,11 @@ public class EndPrevent extends Appliance {
|
||||
public void setEndState(State state) {
|
||||
this.localConfig().set(this.endPreventKey, state.name());
|
||||
Configuration.saveChanges();
|
||||
this.applyEndState(state);
|
||||
}
|
||||
|
||||
/** Applies a runtime state; only manual commands persist it through setEndState(). */
|
||||
void applyEndState(State state) {
|
||||
this.endPreventState = state;
|
||||
this.updateEndBorder();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Datum mit UTC-Offset, z. B. "2026-11-15T18:00:00+01:00"
|
||||
# null bedeutet: kein Termin
|
||||
# Weitere Einträge mit frei wählbaren Namen können ergänzt werden.
|
||||
end-open-at: null
|
||||
# EndPrevent: Bis zu diesem Zeitpunkt bleibt das End geschlossen.
|
||||
end-open-at: "2026-11-08T18:55:34+01:00"
|
||||
# Optional: Nach der End-Öffnung nur die Hauptinsel bis zu diesem Zeitpunkt freigeben.
|
||||
outer-end-open-at: null
|
||||
Reference in new issue
Block a user