Refactor End dimension access management and enhance announcement system
This commit is contained in:
1 parent
02d396db68
commit
45685c0c13
6 files changed
+223
-36
No files matched your search
+56
-21
@@ -2,17 +2,13 @@
|
||||
|
||||
## 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.
|
||||
| Klasse | Aufgabe und Erstellung |
|
||||
| --- | --- |
|
||||
| `DimensionScheduler` | Automatisch geladene Appliance. Erstellt die fehlende `plugins/spawn/dimension-schedule.yml` aus der Vorlage und liest beliebige benannte Zeitpunkte. Kennt keine End-Aktionen. |
|
||||
| `DimensionScheduleCommand` | Wird über die Appliance registriert. `/dimensionSchedule reload` liest die Datei für Konsole und OPs neu ein. Bei Fehlern bleiben alle bisherigen Werte erhalten. |
|
||||
| `EndPrevent` | Besitzt die `EndOpeningEffects`. Erstellt in `onEnable()` eine `EndOpeningSchedule`, wenn die Scheduler-Appliance vorhanden ist, und stoppt sie in `onDisable()`. Verwaltet den gemeinsamen Zustandswechsel. |
|
||||
| `EndOpeningSchedule` | Besitzt einen Bukkit-Task. Startet im nächsten Tick nach der Initialisierung aller Appliances und prüft dann alle 20 Ticks die Uhrzeit und Ankündigungsstufen. |
|
||||
| `EndOpeningEffects` | Enthält die fünf zufälligen Aktivierungstexte und die fünf Ankündigungs-/Öffnungstexte. `Stage` definiert die Vorlaufzeiten, `play(stage)` führt die Stufeneffekte aus. |
|
||||
|
||||
## Öffnungszeiten
|
||||
|
||||
@@ -29,16 +25,55 @@ Ein zukünftiger End-Termin hat Vorrang vor einem bereits vergangenen Outer-End-
|
||||
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
|
||||
## Gemeinsamer Zustandswechsel und 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.
|
||||
Commands und laufende zeitgesteuerte Übergänge verwenden `EndPrevent.setEndState()`.
|
||||
Der gemeinsame Zustandswechsel aktualisiert Zustand und Weltgrenze, speichert Änderungen in
|
||||
`config.yml` und führt beim Wechsel von `CLOSED` zu `NO_OUTER` oder `OPEN` den Effekt `OPENED` aus.
|
||||
Bereits offene Zustände erzeugen bei erneutem Öffnen keine weitere Meldung. Die spätere
|
||||
Freigabe des Outer Ends (`NO_OUTER` zu `OPEN`) wiederholt die End-Öffnungsmeldung ebenfalls nicht.
|
||||
|
||||
Beim Start und bei geänderten Öffnungszeiten verwendet der Zeitplan `restoreEndState()`.
|
||||
Diese Methode führt denselben Zustandswechsel mit Speicherung aus, stellt den aktuellen
|
||||
Zustand aber ohne nachträgliche Öffnungseffekte her. Entfernte Termine heben den zuletzt
|
||||
gesetzten Zustand nicht auf; die Steuerung bleibt dann vollständig manuell.
|
||||
|
||||
Manuelle Commands 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`.
|
||||
|
||||
## Ankündigungen und Portalversuche
|
||||
|
||||
| Stufe | Zeitpunkt und Ausgabe |
|
||||
| --- | --- |
|
||||
| `FOUR_HOURS_BEFORE` | 4 Stunden vor `end-open-at`: unheimliche Stille |
|
||||
| `ONE_HOUR_BEFORE` | 1 Stunde vorher: schwere Luft |
|
||||
| `TEN_MINUTES_BEFORE` | 10 Minuten vorher: Flügelschlagen |
|
||||
| `ONE_MINUTE_BEFORE` | 1 Minute vorher: Schatten |
|
||||
| `OPENED` | Bei einer tatsächlichen Öffnung: Riss in der Realität |
|
||||
|
||||
Vorankündigungen erscheinen einmalig für alle Online-Spieler im Chat, solange das End
|
||||
geschlossen ist. Bereits vergangene Stufen werden beim Start oder bei einem neuen End-Termin
|
||||
übersprungen. Unveränderte Reloads, neue Outer-End-Termine und Änderungen anderer YAML-Einträge
|
||||
setzen die bereits behandelten Stufen nicht zurück. Bei Verzögerungen wird höchstens die
|
||||
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.
|
||||
|
||||
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.
|
||||
Die bestehenden Meldungen beim Betreten gesperrter Portale und Gateways bleiben getrennt davon.
|
||||
|
||||
## Erweiterungen und Leistung
|
||||
|
||||
Die Originaltexte stehen ausschließlich in `EndOpeningEffects`. Zusätzliche Sounds oder
|
||||
andere Effekte können in `play(stage)` ergänzt werden und werden bei manueller sowie
|
||||
zeitgesteuerter Öffnung gleichermaßen ausgeführt. Weitere Vorankündigungen erhalten einen
|
||||
neuen `Stage`-Eintrag mit Vorlaufzeit und Text; der Zeitplan berücksichtigt sie automatisch.
|
||||
|
||||
Es bleibt bei einem Task mit wenigen Zeitvergleichen pro Sekunde und einer kleinen
|
||||
`EnumSet`-Menge für behandelte Stufen. Die Prüfung arbeitet im Arbeitsspeicher. Chat,
|
||||
Zustandswechsel und spätere Bukkit-Effekte laufen auf dem Server-Hauptthread. Gespeichert
|
||||
wird nur bei tatsächlichen Zustandsänderungen in `config.yml`; die Termin-YAML wird nach
|
||||
ihrer erstmaligen Erstellung ausschließlich gelesen. Es gibt keine persistenten Ausführungsstatus.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.endPrevent;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/** All End-opening presentation lives here; add sounds or other effects in play(). */
|
||||
final class EndOpeningEffects {
|
||||
enum Stage {
|
||||
FOUR_HOURS_BEFORE(Duration.ofHours(4),
|
||||
"Eine unheimliche Stille scheint sich langsam über die Welt auszubreiten. Du hast das seltsame Gefühl, beobachtet zu werden ..."),
|
||||
ONE_HOUR_BEFORE(Duration.ofHours(1),
|
||||
"Die Luft fühlt sich plötzlich ungewöhnlich schwer an. Du hast das Gefühl, als würde eine unsichtbare Kraft auf dir lasten ..."),
|
||||
TEN_MINUTES_BEFORE(Duration.ofMinutes(10),
|
||||
"Ein seltsames Geräusch hallt durch die Welt. Könnte es das Schlagen gewaltiger Flügel sein ...?"),
|
||||
ONE_MINUTE_BEFORE(Duration.ofMinutes(1),
|
||||
"Ein gewaltiger Schatten scheint über die Welt zu ziehen. Du blickst zum Himmel, doch dort ist nichts zu sehen ..."),
|
||||
OPENED(Duration.ZERO,
|
||||
"Ein gewaltiger Riss öffnet sich in der Realität! Die Leere erwacht und die letzte Grenze fällt. Der Weg ins End ist nun offen!");
|
||||
|
||||
private final Duration leadTime;
|
||||
private final String message;
|
||||
|
||||
Stage(Duration leadTime, String message) {
|
||||
this.leadTime = leadTime;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
Duration leadTime() {
|
||||
return this.leadTime;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<String> ACTIVATION_DENIED_MESSAGES = List.of(
|
||||
"Die Augen scheinen dich für einen kurzen Moment anzustarren. Dir läuft ein kalter Schauer über den Rücken ...",
|
||||
"Der Rahmen fühlt sich ungewöhnlich kalt an. Du hast das Gefühl, als würde er dir die Wärme aus den Fingern ziehen …",
|
||||
"Die Luft über dem leeren Rahmen scheint ungewöhnlich dunkel. Du hast das Gefühl, in einen bodenlosen Abgrund zu blicken ...",
|
||||
"Für einen kurzen Moment scheint die Umgebung vollkommen still zu werden. Du fragst dich, ob du dir das nur eingebildet hast ...",
|
||||
"Ein schwaches, violettes Licht flackert zwischen den Augen auf. Etwas scheint dich zu beobachten ..."
|
||||
);
|
||||
|
||||
void denyActivation(Player player) {
|
||||
String message = ACTIVATION_DENIED_MESSAGES.get(ThreadLocalRandom.current().nextInt(ACTIVATION_DENIED_MESSAGES.size()));
|
||||
player.sendActionBar(Component.text(message, NamedTextColor.RED));
|
||||
}
|
||||
|
||||
void play(Stage stage) {
|
||||
Component message = Component.text(stage.message, stage == Stage.OPENED ? NamedTextColor.LIGHT_PURPLE : NamedTextColor.GRAY);
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(message));
|
||||
}
|
||||
}
|
||||
+53
-5
@@ -1,12 +1,14 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.endPrevent;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.dimensionScheduler.DimensionScheduler;
|
||||
import eu.mhsl.craftattack.spawn.common.appliances.tooling.endPrevent.EndOpeningEffects.Stage;
|
||||
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.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -14,16 +16,21 @@ import java.util.Optional;
|
||||
* DimensionScheduler only supplies configuration; this class owns the single polling task.
|
||||
*/
|
||||
final class EndOpeningSchedule {
|
||||
private static final EnumSet<Stage> ANNOUNCEMENT_STAGES = EnumSet.complementOf(EnumSet.of(Stage.OPENED));
|
||||
private final EndPrevent endPrevent;
|
||||
private final DimensionScheduler scheduler;
|
||||
private final EndOpeningEffects effects;
|
||||
private final EnumSet<Stage> handledAnnouncements = EnumSet.noneOf(Stage.class);
|
||||
private @Nullable BukkitTask task;
|
||||
private Optional<Instant> previousEndOpenAt = Optional.empty();
|
||||
private Optional<Instant> previousOuterEndOpenAt = Optional.empty();
|
||||
private @Nullable EndPrevent.State previousScheduledState;
|
||||
private boolean initialized;
|
||||
|
||||
EndOpeningSchedule(EndPrevent endPrevent, DimensionScheduler scheduler) {
|
||||
EndOpeningSchedule(EndPrevent endPrevent, DimensionScheduler scheduler, EndOpeningEffects effects) {
|
||||
this.endPrevent = endPrevent;
|
||||
this.scheduler = scheduler;
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
void start() {
|
||||
@@ -41,17 +48,20 @@ final class EndOpeningSchedule {
|
||||
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)
|
||||
Instant now = Instant.now();
|
||||
boolean endTimeChanged = !endOpenAt.equals(this.previousEndOpenAt);
|
||||
boolean restoreSchedule = !this.initialized || endTimeChanged
|
||||
|| !outerEndOpenAt.equals(this.previousOuterEndOpenAt);
|
||||
if(!this.initialized || endTimeChanged) this.resetAnnouncements(endOpenAt, now);
|
||||
this.previousEndOpenAt = endOpenAt;
|
||||
this.previousOuterEndOpenAt = outerEndOpenAt;
|
||||
this.initialized = true;
|
||||
|
||||
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;
|
||||
@@ -63,14 +73,52 @@ final class EndOpeningSchedule {
|
||||
|
||||
// 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(restoreSchedule || scheduledState != this.previousScheduledState) {
|
||||
if(this.endPrevent.getEndPreventState() != scheduledState) {
|
||||
this.endPrevent.applyEndState(scheduledState);
|
||||
if(restoreSchedule) {
|
||||
this.endPrevent.restoreEndState(scheduledState);
|
||||
} else {
|
||||
this.endPrevent.setEndState(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;
|
||||
}
|
||||
this.announceDueStages(endOpenAt, now);
|
||||
}
|
||||
|
||||
/** 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();
|
||||
if(endOpenAt.isEmpty()) return;
|
||||
for(Stage stage : ANNOUNCEMENT_STAGES) {
|
||||
if(endOpenAt.get().minus(stage.leadTime()).isBefore(now)) this.handledAnnouncements.add(stage);
|
||||
}
|
||||
}
|
||||
|
||||
private void announceDueStages(Optional<Instant> endOpenAt, Instant now) {
|
||||
if(endOpenAt.isEmpty()) return;
|
||||
if(!now.isBefore(endOpenAt.get())) {
|
||||
// The opening transition owns OPENED. Never replay teasers after opening is due.
|
||||
this.handledAnnouncements.addAll(ANNOUNCEMENT_STAGES);
|
||||
return;
|
||||
}
|
||||
|
||||
Stage latestDue = null;
|
||||
Instant latestDueAt = null;
|
||||
for(Stage stage : ANNOUNCEMENT_STAGES) {
|
||||
if(this.handledAnnouncements.contains(stage)) continue;
|
||||
Instant dueAt = endOpenAt.get().minus(stage.leadTime());
|
||||
if(now.isBefore(dueAt)) continue;
|
||||
this.handledAnnouncements.add(stage);
|
||||
if(latestDueAt == null || dueAt.isAfter(latestDueAt)) {
|
||||
latestDue = stage;
|
||||
latestDueAt = dueAt;
|
||||
}
|
||||
}
|
||||
// Consume all crossed milestones, but announce only the latest while the End is closed.
|
||||
if(latestDue != null && this.endPrevent.isEndClosed()) this.effects.play(latestDue);
|
||||
}
|
||||
}
|
||||
+21
-6
@@ -15,6 +15,7 @@ import java.util.List;
|
||||
|
||||
public class EndPrevent extends Appliance {
|
||||
private final String endPreventKey = "endPrevent";
|
||||
private final EndOpeningEffects openingEffects = new EndOpeningEffects();
|
||||
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();
|
||||
@@ -40,7 +41,7 @@ public class EndPrevent extends Appliance {
|
||||
.findFirst().orElse(null);
|
||||
if(scheduler == null) return;
|
||||
|
||||
this.openingSchedule = new EndOpeningSchedule(this, scheduler);
|
||||
this.openingSchedule = new EndOpeningSchedule(this, scheduler, this.openingEffects);
|
||||
this.openingSchedule.start();
|
||||
}
|
||||
|
||||
@@ -55,15 +56,29 @@ public class EndPrevent extends Appliance {
|
||||
}
|
||||
|
||||
public void setEndState(State state) {
|
||||
this.localConfig().set(this.endPreventKey, state.name());
|
||||
Configuration.saveChanges();
|
||||
this.applyEndState(state);
|
||||
this.changeEndState(state, true);
|
||||
}
|
||||
|
||||
/** Applies a runtime state; only manual commands persist it through setEndState(). */
|
||||
void applyEndState(State state) {
|
||||
/** Restores a schedule on startup or after changed times without replaying past effects. */
|
||||
void restoreEndState(State state) {
|
||||
this.changeEndState(state, false);
|
||||
}
|
||||
|
||||
/** Shared by commands, live scheduled transitions and silent schedule restoration. */
|
||||
private void changeEndState(State state, boolean announceOpening) {
|
||||
if(this.endPreventState == state) return;
|
||||
boolean wasClosed = this.isEndClosed();
|
||||
this.endPreventState = state;
|
||||
this.updateEndBorder();
|
||||
this.localConfig().set(this.endPreventKey, state.name());
|
||||
Configuration.saveChanges();
|
||||
if(announceOpening && wasClosed && !this.isEndClosed()) {
|
||||
this.openingEffects.play(EndOpeningEffects.Stage.OPENED);
|
||||
}
|
||||
}
|
||||
|
||||
EndOpeningEffects effects() {
|
||||
return this.openingEffects;
|
||||
}
|
||||
|
||||
public boolean isEndClosed() {
|
||||
|
||||
+36
-3
@@ -4,28 +4,55 @@ import com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.data.type.EndPortalFrame;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
class EndPreventListener extends ApplianceListener<EndPrevent> {
|
||||
private final Map<UUID, Integer> lastDeniedActivationTick = new HashMap<>();
|
||||
|
||||
@EventHandler
|
||||
public void onEnderEyeInteraction(PlayerInteractEvent event) {
|
||||
if(event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
if(event.getClickedBlock() == null) return;
|
||||
if(!event.getClickedBlock().getType().equals(Material.END_PORTAL_FRAME)) return;
|
||||
if(!(event.getClickedBlock().getBlockData() instanceof EndPortalFrame frame) || frame.hasEye()) return;
|
||||
if(event.getItem() == null) return;
|
||||
if(!event.getItem().getType().equals(Material.ENDER_EYE)) return;
|
||||
if(!this.getAppliance().isEndClosed()) return;
|
||||
|
||||
this.cancelIfClosed(event, event.getPlayer());
|
||||
// Cancel either hand, but show only one flavour text if both hands fire in the same tick.
|
||||
event.setCancelled(true);
|
||||
Player player = event.getPlayer();
|
||||
int currentTick = Bukkit.getCurrentTick();
|
||||
Integer previousTick = this.lastDeniedActivationTick.put(player.getUniqueId(), currentTick);
|
||||
if(previousTick != null && previousTick == currentTick) return;
|
||||
this.getAppliance().effects().denyActivation(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
this.lastDeniedActivationTick.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerPortal(PlayerPortalEvent event) {
|
||||
if(!event.getTo().getWorld().getEnvironment().equals(World.Environment.THE_END)) return;
|
||||
Location destination = event.getTo();
|
||||
if(destination == null || destination.getWorld() == null) return;
|
||||
if(!destination.getWorld().getEnvironment().equals(World.Environment.THE_END)) return;
|
||||
|
||||
this.cancelIfClosed(event, event.getPlayer());
|
||||
}
|
||||
@@ -38,7 +65,13 @@ class EndPreventListener extends ApplianceListener<EndPrevent> {
|
||||
private void cancelIfClosed(Cancellable event, Player p) {
|
||||
if(!this.getAppliance().isEndClosed()) return;
|
||||
event.setCancelled(true);
|
||||
p.sendActionBar(Component.text("Das End ist nicht freigeschaltet!", NamedTextColor.RED));
|
||||
|
||||
int currentTick = Bukkit.getCurrentTick();
|
||||
Integer previousTick = this.lastDeniedActivationTick.put(p.getUniqueId(), currentTick);
|
||||
if(previousTick != null && previousTick == currentTick) return;
|
||||
this.getAppliance().effects().denyActivation(p);
|
||||
/*event.setCancelled(true);
|
||||
p.sendActionBar(Component.text("Das End ist nicht freigeschaltet!", NamedTextColor.RED));*/
|
||||
}
|
||||
|
||||
private void cancelIfOnlyInner(Cancellable event, Player p) {
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
# null bedeutet: kein Termin
|
||||
# Weitere Einträge mit frei wählbaren Namen können ergänzt werden.
|
||||
# EndPrevent: Bis zu diesem Zeitpunkt bleibt das End geschlossen.
|
||||
end-open-at: "2026-11-08T18:55:34+01:00"
|
||||
end-open-at: "2026-11-08T19:16: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