EternalCode Commons is a modular utility library for Minecraft plugin development. It provides platform-agnostic abstractions for scheduling, time parsing, cooldowns, and more β with ready-made implementations for Bukkit, Paper, and Folia.
| Module | Description | Java | Platform |
|---|---|---|---|
eternalcode-commons-shared |
Core utilities β scheduling API, time parsing, cooldowns, progress bars, lazy init | 17 | Any |
eternalcode-commons-bukkit |
Bukkit/Spigot/Paper scheduler implementation, item utils, position adapters | 17 | Bukkit/Paper |
eternalcode-commons-folia |
Folia scheduler implementation | 17 | Folia |
eternalcode-commons-adventure |
Kyori Adventure helpers β legacy color conversion, auto-clickable URLs | 21 | Any |
eternalcode-commons-loom |
Virtual thread scheduler (platform-agnostic) | 21 | Any |
eternalcode-commons-bukkit-loom |
Virtual thread scheduler for Bukkit/Paper | 21 | Bukkit/Paper |
eternalcode-commons-folia-loom |
Virtual thread scheduler for Folia (region-aware) | 21 | Folia |
eternalcode-commons-updater |
Modrinth update checker | 17 | Any |
Add the EternalCode repository and the modules you need to your build.gradle.kts:
repositories {
maven("https://repo.eternalcode.pl/releases")
}
dependencies {
// Core utilities (scheduling API, time parsing, cooldowns, etc.)
implementation("com.eternalcode:eternalcode-commons-shared:1.4.2")
// Pick your platform's scheduler implementation:
implementation("com.eternalcode:eternalcode-commons-bukkit:1.4.2") // Bukkit/Paper
// implementation("com.eternalcode:eternalcode-commons-folia:1.4.2") // Folia
// Optional: virtual thread scheduler (Java 21+)
implementation("com.eternalcode:eternalcode-commons-bukkit-loom:1.4.2") // Bukkit/Paper
// implementation("com.eternalcode:eternalcode-commons-folia-loom:1.4.2") // Folia
// Optional: Adventure text utilities
implementation("com.eternalcode:eternalcode-commons-adventure:1.4.2")
// Optional: Modrinth update checker
implementation("com.eternalcode:eternalcode-commons-updater:1.4.2")
}The MinecraftScheduler interface wraps Bukkit's scheduler with Duration-based API and async support:
public class MyPlugin extends JavaPlugin {
private MinecraftScheduler scheduler;
@Override
public void onEnable() {
this.scheduler = new BukkitSchedulerImpl(this);
}
public void example(Player player) {
// Run once, async
scheduler.runAsync(() -> {
String data = database.load(player.getUniqueId());
// Switch back to main thread
scheduler.run(() -> player.sendMessage(data));
});
// Repeating task with delay
scheduler.timer(() -> player.sendMessage("tick"), Duration.ofSeconds(1), Duration.ofSeconds(5));
}
}BukkitLoomScheduler provides a fluent async pipeline using Java 21 virtual threads:
public class MyPlugin extends JavaPlugin {
private BukkitLoomScheduler scheduler;
@Override
public void onEnable() {
this.scheduler = BukkitLoomScheduler.create(this);
}
@Override
public void onDisable() {
this.scheduler.shutdown(Duration.ofSeconds(5));
}
public void loadData(Player player) {
scheduler.supplyAsync(() -> database.load(player.getUniqueId())) // virtual thread
.thenApply(data -> transform(data)) // virtual thread
.thenAcceptSync(result -> player.sendMessage(result)) // main thread
.exceptionally(e -> getLogger().severe(e.getMessage()));
}
}Rule:
Async/thenApplyβ virtual thread (never call Bukkit API here).Sync/thenAcceptSyncβ main thread.
Folia has no single main thread β use the correct context for each operation:
FoliaLoomScheduler scheduler = FoliaLoomScheduler.create(plugin);
// Player operations
scheduler.forEntity(player)
.supplyAsync(() -> database.load(player.getUniqueId()))
.thenAcceptSync(data -> player.sendMessage(data)); // player's region thread
// Block operations
scheduler.forLocation(location)
.runSync(() -> location.getBlock().setType(Material.STONE)); // location's region thread
// Global operations (broadcast, plugin state)
scheduler.forGlobal()
.runSync(() -> Bukkit.broadcast(Component.text("Hello!")));Parse human-readable durations from user input:
// Pre-built parsers
Duration duration = DurationParser.TIME_UNITS.parse("1h30m"); // 1 hour 30 minutes
Duration duration2 = DurationParser.DATE_TIME_UNITS.parse("2d12h"); // 2 days 12 hours
// Or build a custom one
TemporalAmountParser<Duration> parser = new DurationParser()
.withUnit("s", ChronoUnit.SECONDS)
.withUnit("m", ChronoUnit.MINUTES);
Duration result = parser.parse("5m30s"); // 5 minutes 30 secondsTrack per-player (or any key) cooldowns backed by Caffeine:
Delay<UUID> cooldown = Delay.withDefault(() -> Duration.ofSeconds(10));
public void onCommand(Player player) {
if (cooldown.hasDelay(player.getUniqueId())) {
Duration remaining = cooldown.getRemaining(player.getUniqueId());
player.sendMessage("Wait " + remaining.toSeconds() + "s");
return;
}
cooldown.markDelay(player.getUniqueId());
// ... execute command
}Render a customizable text progress bar:
ProgressBar bar = ProgressBar.builder()
.length(20)
.filledChar("β")
.emptyChar("β")
.filledColor("Β§a")
.emptyColor("Β§7")
.brackets("Β§8[", "Β§8]")
.build();
String rendered = bar.render(current, max);
// e.g. Β§8[Β§aββββββββββββββββββββΒ§8]Check for new plugin versions on Modrinth:
UpdateChecker checker = new ModrinthUpdateChecker();
UpdateResult result = checker.check("your-modrinth-project-id", new Version(getDescription().getVersion()));
if (result.isUpdateAvailable()) {
getLogger().info("New version available: " + result.latestVersion());
getLogger().info("Download: " + result.downloadUrl());
}Convert legacy &-color codes to Adventure components, and make URLs clickable:
// Parse &-color codes to Adventure component
Component component = AdventureUtil.component("&aHello &bWorld!");
// Auto-linkify URLs in a component
Component withLinks = new AdventureUrlPostProcessor().apply(component);
// Convert Β§-color codes (e.g. from config) to &-codes before parsing
String normalized = new AdventureLegacyColorPreProcessor().apply(rawString);This project is licensed under the GNU General Public License v3.0 β see the LICENSE file for details.