folia-supported: true in plugin.yml). Folia's GlobalRegionScheduler is used automatically when detected at runtime; Paper/Spigot continue to use the Bukkit scheduler.compat/ package hierarchy - clean OOP abstraction layer:
compat.platform.PlatformDetector - detects Folia vs. Paper/Spigot at startup via RegionizedServer class presence.compat.scheduler.SchedulerAdapter / TaskHandle - platform-agnostic scheduler interface (runTask, runTaskTimer, runTaskLater, runTaskAsync).compat.scheduler.BukkitSchedulerAdapter - SchedulerAdapter backed by BukkitScheduler (Paper/Spigot).compat.scheduler.FoliaSchedulerAdapter - SchedulerAdapter backed by GlobalRegionScheduler (Folia); only class-loaded when Folia is detected.compat.scheduler.SchedulerAdapterFactory - chooses the correct adapter with graceful fallback if Folia classes are missing.compat.version.ServerVersionUtil - canonical location for runtime Minecraft version detection (replaces util.ServerVersionUtil).compat.material.MaterialCompat - canonical location for cross-version material resolution (replaces util.MaterialCompat).PluginBootstrap now logs the active scheduler adapter class on enable (e.g. Scheduler: BukkitSchedulerAdapter).util.ServerVersionUtil and util.MaterialCompat are now @Deprecated delegation stubs that forward to their canonical compat.* counterparts; they will be removed in a future release.
All internal scheduler usages (CountdownManager, FireworkShowManager, PatternScheduler, ChatInputListener, all listener.actions.* GUI action classes, SpigotIntegration, SpigotUpdateChecker) now go through SchedulerAdapter instead of Bukkit.getScheduler() / BukkitRunnable directly.
Scoreboard API modernised - ScoreboardDisplay now delegates to compat.scoreboard.ScoreboardCompat. On Paper 1.20.3+ (Criteria.DUMMY available) the modern registerNewObjective(String, Criteria, Component) overload is used; older builds fall back to the legacy string overload. The deprecated Scoreboard.resetScores(String) calls are replaced by objective unregister/re-register via ScoreboardCompat.resetObjective.
Title API modernised - TitleDisplay now delegates to compat.title.TitleCompat. On Paper 1.18+ the Adventure Player.showTitle(Title) / Player.clearTitle() APIs are preferred over the deprecated sendTitle(String...) / resetTitle(). TitleValidator now also accepts showTitle as a valid title capability. A Spigot/legacy string fallback is retained.
Per-player notification targeting - NotificationBuilder.players(Collection<Player>) restricts a notification to specific players. EzCountdownApi.sendNotification(Notification, Collection<Player>) provides an inline alternative without building a Notification first.
Countdown.isVisibleTo(Player) - centralises per-player visibility logic (permission gate + target-player set) in one method; all display handlers now use it instead of duplicated inline checks.
CountdownBuilder.targetPlayers(Collection<Player>) - restrict a persistent countdown's display to specific players.
Developer-friendly exception hierarchy - new com.skyblockexp.ezcountdown.api.exception package:
EzCountdownException - base unchecked exception; catch this for all EzCountdown API errors.CountdownNotFoundException - thrown when referencing a countdown name that does not exist; carries getCountdownName().DuplicateCountdownException - thrown when creating a countdown whose name already exists; carries getCountdownName().InvalidConfigurationException - thrown by NotificationBuilder.build() and future builder validators when configuration is invalid; replaces the generic IllegalStateException.api-version bumped to 1.18 in plugin.yml; minimum supported Minecraft version is Paper/Spigot 1.18. Dialog display continues to require Paper 1.21.7+.
Broadened Java compatibility - release JARs now target Java 17 bytecode (previously Java 21), so the plugin runs on Paper 1.18 - 1.20.4 (Java 17) as well as Paper 1.20.5+ (Java 21). The jdk21 Maven profile also targets Java 17 bytecode.
ServerVersionUtil - new utility class (com.skyblockexp.ezcountdown.util.ServerVersionUtil) for runtime Minecraft version detection; enables future conditional feature gating without hard API dependencies.
Startup guard - onEnable now logs the detected MC and Java version and disables the plugin with a clear error message if the server is too old (MC < 1.18 or Java < 17).
ActionBarDisplay, TitleDisplay, ChatDisplay, BossBarDisplay, ScoreboardDisplay, DialogDisplay) use countdown.isVisibleTo(player) instead of duplicated inline permission checks.NotificationBuilder.build() now throws InvalidConfigurationException (a subclass of EzCountdownException) instead of a plain IllegalStateException.ScoreboardDisplay catch blocks now include NoSuchMethodError so the scoreboard falls back to chat if the String-criteria registerNewObjective overload is ever removed in a future Paper build.1.4.3 to 2.0.0./countdown reload - resumeRunningCountdowns() previously called handler.onStart(), which always sets targetInstant to now + fullDuration, discarding the target_epoch saved in storage. It now calls handler.ensureTarget() instead, which is a no-op when a target is already present. The same guard was added to the legacy fallback path for handler-less countdown types. End-commands that fired once before a reload will no longer fire again unexpectedly due to the countdown silently restarting.
Run flash sales, timed events, and launches with stunning countdowns - ActionBar, BossBar, Titles & Placeholders!