predicate) {
return this;
}
+ public EventBuilder playerFilter(Function extractor, Predicate condition) {
+ filters.add(e -> {
+ P p = extractor.apply(e);
+ return p != null && condition.test(p);
+ });
+ return this;
+ }
+
public EventBuilder maxFires(int count) {
this.maxFires = count;
return this;
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/event/platform/PaperEventBus.java b/oumlib-core/src/main/java/dev/oum/oumlib/event/platform/PaperEventBus.java
index f22745e..9d710c0 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/event/platform/PaperEventBus.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/event/platform/PaperEventBus.java
@@ -2,6 +2,7 @@
import dev.oum.oumlib.event.EventBuilder;
import dev.oum.oumlib.event.ListenerHandle;
+import dev.oum.oumlib.scheduler.Scheduler;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
@@ -11,7 +12,6 @@
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
-import dev.oum.oumlib.scheduler.Scheduler;
public final class PaperEventBus implements EventBusAdapter {
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/AnvilMenu.java b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/AnvilMenu.java
index 715071b..bf0a2ca 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/AnvilMenu.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/AnvilMenu.java
@@ -16,6 +16,7 @@
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.view.AnvilView;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.CheckReturnValue;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
@@ -29,6 +30,7 @@
import java.util.function.Consumer;
@SuppressWarnings({"UnstableApiUsage", "unused"})
+@ApiStatus.Obsolete
public final class AnvilMenu implements Menu {
private static final MiniMessage MM = MiniMessage.miniMessage();
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ChestMenu.java b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ChestMenu.java
index 2c7eacf..093a3a2 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ChestMenu.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ChestMenu.java
@@ -2,6 +2,8 @@
import dev.oum.oumlib.event.Events;
import dev.oum.oumlib.event.ListenerHandle;
+import dev.oum.oumlib.scheduler.Scheduler;
+import dev.oum.oumlib.scheduler.TaskHandle;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
@@ -17,6 +19,7 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -40,10 +43,12 @@ public final class ChestMenu implements Menu {
private final Sound openSound;
private final Sound closeSound;
private final Sound clickSound;
+ private final Duration autoRefresh;
private ListenerHandle clickHandle;
private ListenerHandle dragHandle;
private ListenerHandle closeHandle;
+ private TaskHandle refreshTask;
private ChestMenu(@NonNull Builder builder) {
this.title = builder.title;
@@ -57,6 +62,7 @@ private ChestMenu(@NonNull Builder builder) {
this.openSound = builder.openSound;
this.closeSound = builder.closeSound;
this.clickSound = builder.clickSound;
+ this.autoRefresh = builder.autoRefresh;
}
@Contract(" -> new")
@@ -89,7 +95,7 @@ public void updateState(@NonNull Player player, @NonNull String key, @Nullable O
}
@Override
- public void open(Player player) {
+ public void open(@NonNull Player player) {
Map state = playerStates.computeIfAbsent(player.getUniqueId(), uuid -> {
Map map = new HashMap<>();
stateProviders.forEach((key, provider) -> map.put(key, provider.apply(player)));
@@ -110,6 +116,16 @@ public void open(Player player) {
if (openSound != null) {
player.playSound(openSound);
}
+ if (autoRefresh != null && refreshTask == null) {
+ refreshTask = Scheduler.runRepeating(autoRefresh, autoRefresh, () -> {
+ for (UUID uuid : open.keySet()) {
+ Player p = Bukkit.getPlayer(uuid);
+ if (p != null && p.isOnline()) {
+ refresh(p);
+ }
+ }
+ });
+ }
}
@Override
@@ -122,6 +138,10 @@ public void close(@NonNull Player player) {
}
if (open.isEmpty()) {
unregisterListeners();
+ if (refreshTask != null) {
+ refreshTask.cancel();
+ refreshTask = null;
+ }
}
}
@@ -236,6 +256,10 @@ private synchronized void registerListeners() {
playerStates.remove(player.getUniqueId());
if (open.isEmpty()) {
unregisterListeners();
+ if (refreshTask != null) {
+ refreshTask.cancel();
+ refreshTask = null;
+ }
}
}
});
@@ -282,6 +306,13 @@ public static final class Builder {
private Sound openSound;
private Sound closeSound;
private Sound clickSound;
+ private Duration autoRefresh;
+
+ @CheckReturnValue
+ public @NonNull Builder autoRefresh(@Nullable Duration duration) {
+ this.autoRefresh = duration;
+ return this;
+ }
@CheckReturnValue
public @NonNull Builder openSound(@Nullable Sound sound) {
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ItemBuilder.java b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ItemBuilder.java
index ef64959..493a5ec 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ItemBuilder.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/ItemBuilder.java
@@ -4,6 +4,9 @@
import com.google.gson.Gson;
import dev.oum.oumlib.OumLib;
import dev.oum.oumlib.util.ItemSerializer;
+import io.papermc.paper.datacomponent.DataComponentType;
+import io.papermc.paper.datacomponent.DataComponentTypes;
+import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
@@ -17,6 +20,7 @@
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.profile.PlayerTextures;
+import org.bukkit.tag.DamageTypeTags;
import org.jetbrains.annotations.CheckReturnValue;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
@@ -25,10 +29,8 @@
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
import java.util.stream.Collectors;
public final class ItemBuilder {
@@ -130,8 +132,7 @@ private ItemBuilder(@NonNull ItemStack item) {
@CheckReturnValue
public @NonNull ItemBuilder glow() {
- meta.addEnchant(Enchantment.UNBREAKING, 1, true);
- meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
+ meta.setEnchantmentGlintOverride(true);
return this;
}
@@ -154,6 +155,67 @@ private ItemBuilder(@NonNull ItemStack item) {
return this;
}
+ @CheckReturnValue
+ public @NonNull ItemBuilder itemModel(@NonNull Key key) {
+ meta.setItemModel(NamespacedKey.fromString(key.asString()));
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder itemModel(@NonNull NamespacedKey key) {
+ meta.setItemModel(key);
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder glintOverride(boolean glint) {
+ meta.setEnchantmentGlintOverride(glint);
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder maxStackSize(int maxStackSize) {
+ meta.setMaxStackSize(maxStackSize);
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder maxDamage(int maxDamage) {
+ stack.setData(DataComponentTypes.MAX_DAMAGE, maxDamage);
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder fireResistant(boolean resistant) {
+ if (resistant) {
+ meta.setDamageResistant(DamageTypeTags.IS_FIRE);
+ } else {
+ meta.setDamageResistant(null);
+ }
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder hideTooltip(boolean hide) {
+ meta.setHideTooltip(hide);
+ return this;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @CheckReturnValue
+ public @NonNull ItemBuilder data(DataComponentType.@NonNull Valued type, @NonNull Object value) {
+ stack.setItemMeta(meta);
+ stack.setData(type, value);
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull ItemBuilder removeData(@NonNull DataComponentType type) {
+ stack.setItemMeta(meta);
+ stack.unsetData(type);
+ return this;
+ }
+
@CheckReturnValue
public @NonNull ItemBuilder pdc(@NonNull String key, @NonNull String value) {
NamespacedKey nsk = new NamespacedKey(OumLib.plugin(), key);
@@ -238,7 +300,28 @@ private ItemBuilder(@NonNull ItemStack item) {
try {
PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID(), null);
PlayerTextures textures = profile.getTextures();
- URL url = URI.create("https://textures.minecraft.net/texture/" + textureValue).toURL();
+
+ String targetUrl = textureValue;
+ if (textureValue.startsWith("ey")) {
+ try {
+ String decodedStr = new String(Base64.getDecoder().decode(textureValue), StandardCharsets.UTF_8);
+ int index = decodedStr.indexOf("\"url\":\"");
+ if (index != -1) {
+ int start = index + 7;
+ int end = decodedStr.indexOf("\"", start);
+ if (end != -1) {
+ targetUrl = decodedStr.substring(start, end);
+ }
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+
+ if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
+ targetUrl = "https://textures.minecraft.net/texture/" + targetUrl;
+ }
+
+ URL url = URI.create(targetUrl).toURL();
textures.setSkin(url);
profile.setTextures(textures);
skullMeta.setPlayerProfile(profile);
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/PaginatedMenu.java b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/PaginatedMenu.java
index 272fd3e..1c2e6e2 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/inventory/PaginatedMenu.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/inventory/PaginatedMenu.java
@@ -13,6 +13,7 @@
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.CheckReturnValue;
import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import java.util.*;
@@ -29,7 +30,7 @@ public final class PaginatedMenu implements Menu {
private final int nextSlot;
private final Function prevButton;
private final Function nextButton;
- private final List items;
+ private final Function> itemsSupplier;
private final PaginatedClickHandler clickHandler;
private final Map pages = new HashMap<>();
private final Map open = new HashMap<>();
@@ -45,7 +46,7 @@ private PaginatedMenu(@NonNull Builder builder) {
this.nextSlot = builder.nextSlot;
this.prevButton = builder.prevButton;
this.nextButton = builder.nextButton;
- this.items = List.copyOf(builder.items);
+ this.itemsSupplier = builder.itemsSupplier;
this.clickHandler = builder.clickHandler;
}
@@ -56,7 +57,17 @@ private PaginatedMenu(@NonNull Builder builder) {
}
public int totalPages() {
- return Math.max(1, (int) Math.ceil((double) items.size() / contentSlots.length));
+ try {
+ return totalPages(null);
+ } catch (Exception e) {
+ return 1;
+ }
+ }
+
+ public int totalPages(@Nullable Player player) {
+ List list = itemsSupplier.apply(player);
+ int size = list != null ? list.size() : 0;
+ return Math.max(1, (int) Math.ceil((double) size / contentSlots.length));
}
@Override
@@ -80,7 +91,7 @@ public void close(@NonNull Player player) {
public void refresh(@NonNull Player player) {
Inventory inv = open.get(player.getUniqueId());
if (inv == null) return;
- populateItems(inv, pages.getOrDefault(player.getUniqueId(), 1));
+ populateItems(player, inv, pages.getOrDefault(player.getUniqueId(), 1));
player.updateInventory();
}
@@ -89,7 +100,7 @@ private void reopen(@NonNull Player player) {
int page = pages.getOrDefault(player.getUniqueId(), 1);
String resolvedTitle = title
.replace("", String.valueOf(page))
- .replace("", String.valueOf(totalPages()));
+ .replace("", String.valueOf(totalPages(player)));
var titleComponent = MM.deserialize(resolvedTitle);
Inventory inv = open.get(player.getUniqueId());
@@ -104,27 +115,29 @@ private void reopen(@NonNull Player player) {
}
} catch (Exception ignored) {
}
- populateItems(inv, page);
+ populateItems(player, inv, page);
player.updateInventory();
} else {
inv = Bukkit.createInventory(null, rows * 9, titleComponent);
- populateItems(inv, page);
+ populateItems(player, inv, page);
open.put(player.getUniqueId(), inv);
player.openInventory(inv);
}
}
- private void populateItems(@NonNull Inventory inv, int page) {
+ private void populateItems(@NonNull Player player, @NonNull Inventory inv, int page) {
inv.clear();
+ List list = itemsSupplier.apply(player);
+ if (list == null) list = List.of();
int start = (page - 1) * contentSlots.length;
for (int i = 0; i < contentSlots.length; i++) {
int idx = start + i;
- if (idx < items.size()) inv.setItem(contentSlots[i], items.get(idx));
+ if (idx < list.size()) inv.setItem(contentSlots[i], list.get(idx));
}
ItemStack prev = page > 1
? prevButton.apply(page)
: ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE).name("Previous").build();
- ItemStack next = page < totalPages()
+ ItemStack next = page < totalPages(player)
? nextButton.apply(page)
: ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE).name("Next").build();
inv.setItem(prevSlot, prev);
@@ -146,20 +159,22 @@ private synchronized void registerListeners() {
pages.put(player.getUniqueId(), page - 1);
reopen(player);
return;
- } else if (slot == nextSlot && page < totalPages()) {
+ } else if (slot == nextSlot && page < totalPages(player)) {
pages.put(player.getUniqueId(), page + 1);
reopen(player);
return;
}
if (clickHandler != null) {
+ List list = itemsSupplier.apply(player);
+ if (list == null) list = List.of();
for (int i = 0; i < contentSlots.length; i++) {
if (contentSlots[i] == slot) {
int idx = (page - 1) * contentSlots.length + i;
- if (idx < items.size()) {
+ if (idx < list.size()) {
clickHandler.onClick(
new ClickContext(player, ClickAction.from(event.getClick()), slot, this),
- items.get(idx),
+ list.get(idx),
idx
);
}
@@ -213,7 +228,7 @@ public interface PaginatedClickHandler {
public static final class Builder {
- private final List items = new ArrayList<>();
+ private Function> itemsSupplier = p -> new ArrayList<>();
private String title = "Page /";
private int rows = 6;
private int[] contentSlots = {10, 11, 12, 13, 14, 15, 16};
@@ -266,7 +281,13 @@ public static final class Builder {
@CheckReturnValue
public @NonNull Builder items(@NonNull List<@NonNull ItemStack> items) {
- this.items.addAll(items);
+ this.itemsSupplier = player -> items;
+ return this;
+ }
+
+ @CheckReturnValue
+ public @NonNull Builder items(@NonNull Function<@NonNull Player, @NonNull List<@NonNull ItemStack>> supplier) {
+ this.itemsSupplier = supplier;
return this;
}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Promise.java b/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Promise.java
index d923a1e..1844035 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Promise.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Promise.java
@@ -17,6 +17,10 @@ private Promise(CompletableFuture future) {
this.future = future;
}
+ public static @NonNull Promise fromCompletableFuture(@NonNull CompletableFuture future) {
+ return new Promise<>(future);
+ }
+
@Contract("_ -> new")
public static @NonNull Promise supplyAsync(@NonNull Supplier supplier) {
CompletableFuture fut = new CompletableFuture<>();
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Scheduler.java b/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Scheduler.java
index 21da570..e3a269b 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Scheduler.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/scheduler/Scheduler.java
@@ -42,18 +42,6 @@ private static SchedulerAdapter adapter() {
return adapter().runLater(ticks, task);
}
- @Contract("_, _ -> new")
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @NonNull TaskHandle runDelayed(Duration delay, Runnable task) {
- return runLater(delay, task);
- }
-
- @Contract("_, _ -> new")
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @NonNull TaskHandle runDelayed(long ticks, Runnable task) {
- return runLater(ticks, task);
- }
-
@Contract("_, _, _ -> new")
public static @NonNull TaskHandle runRepeating(Duration initialDelay, Duration interval, Runnable task) {
return adapter().runRepeating(initialDelay, interval, task);
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/text/BukkitText.java b/oumlib-core/src/main/java/dev/oum/oumlib/text/BukkitText.java
deleted file mode 100644
index d31cb16..0000000
--- a/oumlib-core/src/main/java/dev/oum/oumlib/text/BukkitText.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package dev.oum.oumlib.text;
-
-import org.bukkit.Bukkit;
-
-@Deprecated(forRemoval = true, since = "1.0.4")
-public final class BukkitText {
-
- private BukkitText() {
- }
-
- public static void console(String message) {
- Bukkit.getConsoleSender().sendMessage(Text.parse(message));
- }
-
- public static void broadcast(String message) {
- Bukkit.getOnlinePlayers().forEach(p -> Text.send(p, message));
- }
-
- public static void broadcast(String message, String permission) {
- Bukkit.getOnlinePlayers().stream()
- .filter(p -> p.hasPermission(permission))
- .forEach(p -> Text.send(p, message));
- }
-}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/text/Localization.java b/oumlib-core/src/main/java/dev/oum/oumlib/text/Localization.java
index 2fffa58..8a93226 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/text/Localization.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/text/Localization.java
@@ -5,7 +5,7 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
-import org.bukkit.entity.Player;
+
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -92,8 +92,12 @@ private static void flatten(@NonNull String prefix, @NonNull Map
public static @NonNull Component translateFor(@NonNull Object playerObj, @NonNull String key, TagResolver... resolvers) {
String locale = defaultLang;
try {
- if (playerObj instanceof Player p) {
- locale = p.locale().getLanguage();
+ Class> bukkitPlayerClass = Class.forName("org.bukkit.entity.Player");
+ if (bukkitPlayerClass.isInstance(playerObj)) {
+ Locale loc = (Locale) playerObj.getClass().getMethod("locale").invoke(playerObj);
+ if (loc != null) {
+ locale = loc.getLanguage();
+ }
} else {
Class> velocityPlayerClass = Class.forName("com.velocitypowered.api.proxy.Player");
if (velocityPlayerClass.isInstance(playerObj)) {
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/BossBars.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/BossBars.java
deleted file mode 100644
index 467a356..0000000
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/BossBars.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package dev.oum.oumlib.util;
-
-import dev.oum.oumlib.text.Text;
-import net.kyori.adventure.audience.Audience;
-import net.kyori.adventure.bossbar.BossBar;
-import org.jspecify.annotations.NonNull;
-
-import java.time.Duration;
-
-@Deprecated(since = "1.0.1", forRemoval = true)
-public final class BossBars {
-
- private BossBars() {
- }
-
- @Deprecated(since = "1.0.1", forRemoval = true)
- public static @NonNull BossBar show(@NonNull Audience audience, @NonNull String titleMiniMessage,
- float progress, BossBar.@NonNull Color color, BossBar.@NonNull Overlay overlay) {
- return Text.bossBar(audience, titleMiniMessage, progress, color, overlay);
- }
-
- @Deprecated(since = "1.0.1", forRemoval = true)
- public static void showTemporary(@NonNull Audience audience, @NonNull String titleMiniMessage,
- float progress, BossBar.@NonNull Color color, BossBar.@NonNull Overlay overlay,
- @NonNull Duration duration) {
- Text.bossBarTemporary(audience, titleMiniMessage, progress, color, overlay, duration);
- }
-}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Countdown.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Countdown.java
new file mode 100644
index 0000000..afeb9dd
--- /dev/null
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Countdown.java
@@ -0,0 +1,199 @@
+package dev.oum.oumlib.util;
+
+import dev.oum.oumlib.effect.Sounds;
+import dev.oum.oumlib.scheduler.Scheduler;
+import dev.oum.oumlib.scheduler.TaskHandle;
+import net.kyori.adventure.audience.Audience;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.title.Title;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public final class Countdown {
+
+ private final Audience audience;
+ private final int totalSeconds;
+ private final Display displayMode;
+ private final Function formatFunction;
+ private final BiConsumer onTick;
+ private final Consumer onComplete;
+ private final Sounds.Effect tickSound;
+ private final Predicate displayFilter;
+ private int secondsRemaining;
+ private TaskHandle task;
+ @Contract(pure = true)
+ private Countdown(@NonNull Builder builder) {
+ this.audience = builder.audience;
+ this.totalSeconds = builder.totalSeconds;
+ this.displayMode = builder.displayMode;
+ this.formatFunction = builder.formatFunction;
+ this.onTick = builder.onTick;
+ this.onComplete = builder.onComplete;
+ this.tickSound = builder.tickSound;
+ this.displayFilter = builder.displayFilter;
+ this.secondsRemaining = totalSeconds;
+ }
+
+ public static @NonNull Builder builder() {
+ return new Builder();
+ }
+
+ public @NonNull TaskHandle start() {
+ if (task != null) {
+ return task;
+ }
+
+ task = Scheduler.runRepeating(Duration.ZERO, Duration.ofSeconds(1), () -> {
+ if (secondsRemaining <= 0) {
+ if (onComplete != null) {
+ onComplete.accept(audience);
+ }
+ cancel();
+ return;
+ }
+
+ if (displayFilter.test(secondsRemaining)) {
+ String formatted = formatFunction.apply(secondsRemaining);
+ Component message = MiniMessage.miniMessage().deserialize(formatted);
+
+ if (displayMode == Display.TITLE) {
+ audience.showTitle(Title.title(message, Component.empty()));
+ } else if (displayMode == Display.ACTION_BAR) {
+ audience.sendActionBar(message);
+ } else if (displayMode == Display.CHAT) {
+ audience.sendMessage(message);
+ }
+
+ if (tickSound != null && audience instanceof Player player) {
+ tickSound.play(player);
+ }
+ }
+
+ if (onTick != null) {
+ onTick.accept(audience, secondsRemaining);
+ }
+
+ secondsRemaining--;
+ });
+
+ return task;
+ }
+
+ public void cancel() {
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+ }
+
+ public enum Display {
+ TITLE,
+ ACTION_BAR,
+ CHAT
+ }
+
+ public static final class Builder {
+ private Audience audience;
+ private int totalSeconds = 5;
+ private Display displayMode = Display.TITLE;
+ private Function formatFunction = s -> "" + s;
+ private BiConsumer onTick;
+ private Consumer onComplete;
+ private Sounds.Effect tickSound;
+ private Predicate displayFilter;
+
+ private Builder() {
+ }
+
+ public @NonNull Builder audience(@NonNull Audience audience) {
+ this.audience = audience;
+ return this;
+ }
+
+ public @NonNull Builder duration(@NonNull Duration duration) {
+ this.totalSeconds = (int) duration.getSeconds();
+ return this;
+ }
+
+ public @NonNull Builder seconds(int seconds) {
+ this.totalSeconds = seconds;
+ return this;
+ }
+
+ public @NonNull Builder display(@NonNull Display mode) {
+ this.displayMode = mode;
+ return this;
+ }
+
+ public @NonNull Builder format(@NonNull Function format) {
+ this.formatFunction = format;
+ return this;
+ }
+
+ public @NonNull Builder format(@NonNull String template) {
+ this.formatFunction = s -> template
+ .replace("%time%", String.valueOf(s))
+ .replace("%duration%", Format.duration(Duration.ofSeconds(s)))
+ .replace("%digital%", Format.digitalTime(Duration.ofSeconds(s)));
+ return this;
+ }
+
+ public @NonNull Builder onTick(@Nullable BiConsumer onTick) {
+ this.onTick = onTick;
+ return this;
+ }
+
+ public @NonNull Builder onComplete(@Nullable Consumer onComplete) {
+ this.onComplete = onComplete;
+ return this;
+ }
+
+ public @NonNull Builder sound(Sounds.@Nullable Effect sound) {
+ this.tickSound = sound;
+ return this;
+ }
+
+ public @NonNull Builder displayFilter(@NonNull Predicate filter) {
+ this.displayFilter = filter;
+ return this;
+ }
+
+ public @NonNull Builder intervals(int... seconds) {
+ Set set = new HashSet<>();
+ for (int s : seconds) {
+ set.add(s);
+ }
+ this.displayFilter = set::contains;
+ return this;
+ }
+
+ public @NonNull Countdown build() {
+ if (audience == null) {
+ throw new IllegalStateException("Audience must be set.");
+ }
+ if (displayFilter == null) {
+ if (displayMode == Display.CHAT) {
+ displayFilter = s -> s % 10 == 0 || s <= 5;
+ } else {
+ displayFilter = s -> true;
+ }
+ }
+ return new Countdown(this);
+ }
+
+ public @NonNull TaskHandle start() {
+ return build().start();
+ }
+ }
+}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Format.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Format.java
index f787e81..9680596 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/Format.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Format.java
@@ -12,6 +12,8 @@ public final class Format {
private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#,###.##");
private static final String[] SUFFIXES = {"", "k", "M", "B", "T"};
+ private static final int[] ROMAN_VALUES = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
+ private static final String[] ROMAN_SYMBOLS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
private Format() {
}
@@ -83,4 +85,37 @@ public static String compactNumber(double number) {
}
return Duration.ofSeconds(totalSeconds);
}
+
+ public static @NonNull String percentage(double value, double max) {
+ if (max <= 0) return "0.0%";
+ double pct = (value / max) * 100.0;
+ return String.format(Locale.ROOT, "%.1f%%", pct);
+ }
+
+ public static @NonNull String roman(int number) {
+ if (number <= 0) return "";
+ StringBuilder sb = new StringBuilder();
+ int remaining = number;
+ for (int i = 0; i < ROMAN_VALUES.length; i++) {
+ while (remaining >= ROMAN_VALUES[i]) {
+ sb.append(ROMAN_SYMBOLS[i]);
+ remaining -= ROMAN_VALUES[i];
+ }
+ }
+ return sb.toString();
+ }
+
+ public static @NonNull String ordinal(int number) {
+ int mod100 = number % 100;
+ int mod10 = number % 10;
+ if (mod100 >= 11 && mod100 <= 13) {
+ return number + "th";
+ }
+ return switch (mod10) {
+ case 1 -> number + "st";
+ case 2 -> number + "nd";
+ case 3 -> number + "rd";
+ default -> number + "th";
+ };
+ }
}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/ItemSerializer.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/ItemSerializer.java
index 1ec4b08..6da9963 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/ItemSerializer.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/ItemSerializer.java
@@ -95,6 +95,7 @@ private ItemSerializer() {
}
}
+ @SuppressWarnings("deprecation")
private static byte[] serializeToBytes(@NonNull ItemStack item) throws Exception {
if (paperBytesSupported) {
return (byte[]) serializeAsBytesMethod.invoke(item);
@@ -106,6 +107,7 @@ private static byte[] serializeToBytes(@NonNull ItemStack item) throws Exception
}
}
+ @SuppressWarnings("deprecation")
private static @NonNull ItemStack deserializeFromBytes(byte @NonNull [] bytes) throws Exception {
if (paperBytesSupported) {
return (ItemStack) deserializeBytesMethod.invoke(null, (Object) bytes);
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Locations.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Locations.java
index 57b89c7..e22ba69 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/Locations.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Locations.java
@@ -8,6 +8,8 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+import java.util.concurrent.ThreadLocalRandom;
+
public final class Locations {
private Locations() {
@@ -74,4 +76,53 @@ private Locations() {
return null;
}
}
+
+ public static double distance2D(@NonNull Location a, @NonNull Location b) {
+ double dx = a.getX() - b.getX();
+ double dz = a.getZ() - b.getZ();
+ return Math.sqrt(dx * dx + dz * dz);
+ }
+
+ public static @NonNull Location midpoint(@NonNull Location a, @NonNull Location b) {
+ return new Location(
+ a.getWorld(),
+ (a.getX() + b.getX()) / 2.0,
+ (a.getY() + b.getY()) / 2.0,
+ (a.getZ() + b.getZ()) / 2.0,
+ (a.getYaw() + b.getYaw()) / 2.0f,
+ (a.getPitch() + b.getPitch()) / 2.0f
+ );
+ }
+
+ public static @NonNull Location centerBlock(@NonNull Location block) {
+ return new Location(
+ block.getWorld(),
+ block.getBlockX() + 0.5,
+ block.getBlockY() + 0.5,
+ block.getBlockZ() + 0.5,
+ block.getYaw(),
+ block.getPitch()
+ );
+ }
+
+ public static boolean isWithinAABB(@NonNull Location loc, @NonNull Location min, @NonNull Location max) {
+ double minX = Math.min(min.getX(), max.getX());
+ double minY = Math.min(min.getY(), max.getY());
+ double minZ = Math.min(min.getZ(), max.getZ());
+ double maxX = Math.max(min.getX(), max.getX());
+ double maxY = Math.max(min.getY(), max.getY());
+ double maxZ = Math.max(min.getZ(), max.getZ());
+
+ return loc.getX() >= minX && loc.getX() <= maxX
+ && loc.getY() >= minY && loc.getY() <= maxY
+ && loc.getZ() >= minZ && loc.getZ() <= maxZ;
+ }
+
+ public static @NonNull Location randomInRadius(@NonNull Location center, double radius) {
+ double angle = ThreadLocalRandom.current().nextDouble() * 2 * Math.PI;
+ double r = ThreadLocalRandom.current().nextDouble() * radius;
+ double x = center.getX() + r * Math.cos(angle);
+ double z = center.getZ() + r * Math.sin(angle);
+ return new Location(center.getWorld(), x, center.getY(), z, center.getYaw(), center.getPitch());
+ }
}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Pdc.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Pdc.java
index 853d0d2..c352aee 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/Pdc.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Pdc.java
@@ -61,36 +61,6 @@ private static void triggerListeners(@NonNull Object target, @NonNull Namespaced
return new PdcItem(item);
}
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @Nullable String get(@NonNull ItemStack item, @NonNull String key) {
- return of(item).get(key);
- }
-
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @Nullable Integer getInt(@NonNull ItemStack item, @NonNull String key) {
- return of(item).getInt(key);
- }
-
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @Nullable Double getDouble(@NonNull ItemStack item, @NonNull String key) {
- return of(item).getDouble(key);
- }
-
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @NonNull Boolean getBoolean(@NonNull ItemStack item, @NonNull String key) {
- return of(item).getBoolean(key);
- }
-
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @Nullable Long getLong(@NonNull ItemStack item, @NonNull String key) {
- return of(item).getLong(key);
- }
-
- @Deprecated(since = "1.0.4", forRemoval = true)
- public static @Nullable List getList(@NonNull ItemStack item, @NonNull String key) {
- return of(item).getList(key);
- }
-
public interface PdcChangeListener {
void onChange(@NonNull Object target, @NonNull NamespacedKey key,
@Nullable Object oldValue, @Nullable Object newValue);
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/PlayerData.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/PlayerData.java
deleted file mode 100644
index 56a6059..0000000
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/PlayerData.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package dev.oum.oumlib.util;
-
-import org.bukkit.entity.Player;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.List;
-
-@Deprecated(since = "1.0.4", forRemoval = true)
-public final class PlayerData {
-
- private final Player player;
- private final Pdc.PdcHolder holder;
-
- private PlayerData(@NonNull Player player) {
- this.player = player;
- this.holder = Pdc.of(player);
- }
-
- public static @NonNull PlayerData of(@NonNull Player player) {
- return new PlayerData(player);
- }
-
- public @NonNull Player player() {
- return player;
- }
-
- public void set(@NonNull String key, @Nullable String value) {
- holder.set(key, value);
- }
-
- public @Nullable String get(@NonNull String key) {
- return holder.get(key);
- }
-
- public @NonNull String getOrDefault(@NonNull String key, @NonNull String def) {
- return holder.getOrDefault(key, def);
- }
-
- public void setInt(@NonNull String key, int value) {
- holder.setInt(key, value);
- }
-
- public @Nullable Integer getInt(@NonNull String key) {
- return holder.getInt(key);
- }
-
- public int getIntOrDefault(@NonNull String key, int def) {
- return holder.getIntOrDefault(key, def);
- }
-
- public void setDouble(@NonNull String key, double value) {
- holder.setDouble(key, value);
- }
-
- public @Nullable Double getDouble(@NonNull String key) {
- return holder.getDouble(key);
- }
-
- public double getDoubleOrDefault(@NonNull String key, double def) {
- return holder.getDoubleOrDefault(key, def);
- }
-
- public void setBoolean(@NonNull String key, boolean value) {
- holder.setBoolean(key, value);
- }
-
- public boolean getBoolean(@NonNull String key) {
- return holder.getBoolean(key);
- }
-
- public boolean getBooleanOrDefault(@NonNull String key, boolean def) {
- return holder.getBooleanOrDefault(key, def);
- }
-
- public void setLong(@NonNull String key, long value) {
- holder.setLong(key, value);
- }
-
- public @Nullable Long getLong(@NonNull String key) {
- return holder.getLong(key);
- }
-
- public long getLongOrDefault(@NonNull String key, long def) {
- return holder.getLongOrDefault(key, def);
- }
-
- public void setList(@NonNull String key, @Nullable List value) {
- holder.setList(key, value);
- }
-
- public @Nullable List getList(@NonNull String key) {
- return holder.getList(key);
- }
-
- public void remove(@NonNull String key) {
- holder.remove(key);
- }
-
- public boolean has(@NonNull String key) {
- return holder.has(key);
- }
-}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Players.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Players.java
index 279d65f..2379b0f 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/Players.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Players.java
@@ -7,6 +7,7 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+@Deprecated(since = "1.0.7", forRemoval = true)
public final class Players {
private Players() {
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/util/Proxy.java b/oumlib-core/src/main/java/dev/oum/oumlib/util/Proxy.java
index a2e7acc..a7456ee 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/util/Proxy.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/util/Proxy.java
@@ -1,5 +1,7 @@
package dev.oum.oumlib.util;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.player.KickedFromServerEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
@@ -7,19 +9,25 @@
import com.velocitypowered.api.proxy.server.ServerInfo;
import dev.oum.oumlib.OumLib;
import dev.oum.oumlib.event.Events;
+import dev.oum.oumlib.scheduler.Promise;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.title.Title;
import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
import java.net.InetSocketAddress;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
public final class Proxy {
+ private static final Map> SERVER_GROUPS = new ConcurrentHashMap<>();
+
private Proxy() {
}
@@ -99,12 +107,14 @@ public static boolean unregisterServer(@NonNull String name) {
/**
* Pings a server asynchronously to determine if it is online.
*/
- public static @NonNull CompletableFuture isOnline(@NonNull String serverName) {
- return OumLib.proxy().getServer(serverName)
- .map(server -> server.ping()
- .thenApply(ping -> true)
- .exceptionally(err -> false))
- .orElseGet(() -> CompletableFuture.completedFuture(false));
+ public static @NonNull Promise isOnline(@NonNull String serverName) {
+ return Promise.fromCompletableFuture(
+ OumLib.proxy().getServer(serverName)
+ .map(server -> server.ping()
+ .thenApply(ping -> true)
+ .exceptionally(err -> false))
+ .orElseGet(() -> CompletableFuture.completedFuture(false))
+ );
}
/**
@@ -136,7 +146,7 @@ public static void sendTitleTo(@NonNull String serverName, @NonNull String title
/**
* Finds the least populated online server from the provided list.
*/
- public static @NonNull CompletableFuture> getBestServer(@NonNull List serverNames) {
+ public static @NonNull Promise> getBestServer(@NonNull List serverNames) {
var futures = serverNames.stream()
.map(name -> OumLib.proxy().getServer(name))
.flatMap(Optional::stream)
@@ -145,22 +155,153 @@ public static void sendTitleTo(@NonNull String serverName, @NonNull String title
.exceptionally(err -> null))
.toList();
- return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
- .thenApply(v -> {
- RegisteredServer best = null;
- int minPlayers = Integer.MAX_VALUE;
-
- for (var future : futures) {
- try {
- var entry = future.getNow(null);
- if (entry != null && entry.getValue() < minPlayers) {
- minPlayers = entry.getValue();
- best = entry.getKey();
+ return Promise.fromCompletableFuture(
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+ .thenApply(v -> {
+ RegisteredServer best = null;
+ int minPlayers = Integer.MAX_VALUE;
+
+ for (var future : futures) {
+ try {
+ var entry = future.getNow(null);
+ if (entry != null && entry.getValue() < minPlayers) {
+ minPlayers = entry.getValue();
+ best = entry.getKey();
+ }
+ } catch (Exception ignored) {
+ }
}
- } catch (Exception ignored) {
- }
+ return Optional.ofNullable(best);
+ })
+ );
+ }
+
+ public static void registerGroup(@NonNull String groupName, @NonNull List serverNames) {
+ SERVER_GROUPS.put(groupName.toLowerCase(Locale.ROOT), List.copyOf(serverNames));
+ }
+
+ public static @NonNull List getGroupServers(@NonNull String groupName) {
+ List names = SERVER_GROUPS.get(groupName.toLowerCase(Locale.ROOT));
+ if (names == null) return List.of();
+ return names.stream()
+ .map(name -> OumLib.proxy().getServer(name))
+ .flatMap(Optional::stream)
+ .toList();
+ }
+
+ public static @NonNull Promise connectLeastPopulated(@NonNull Player player, @NonNull String groupName) {
+ List names = SERVER_GROUPS.get(groupName.toLowerCase(Locale.ROOT));
+ if (names == null || names.isEmpty())
+ return Promise.fromCompletableFuture(CompletableFuture.completedFuture(false));
+ return Promise.fromCompletableFuture(
+ getBestServer(names).toCompletableFuture().thenCompose(opt -> {
+ if (opt.isPresent()) {
+ player.createConnectionRequest(opt.get()).fireAndForget();
+ return CompletableFuture.completedFuture(true);
}
- return Optional.ofNullable(best);
- });
+ return CompletableFuture.completedFuture(false);
+ })
+ );
+ }
+
+ public static @NonNull Promise connectRandom(@NonNull Player player, @NonNull String groupName) {
+ List servers = getGroupServers(groupName);
+ if (servers.isEmpty()) return Promise.fromCompletableFuture(CompletableFuture.completedFuture(false));
+ int idx = ThreadLocalRandom.current().nextInt(servers.size());
+ player.createConnectionRequest(servers.get(idx)).fireAndForget();
+ return Promise.fromCompletableFuture(CompletableFuture.completedFuture(true));
+ }
+
+ public static void registerFallbackRouter(@NonNull Object plugin, @NonNull String groupName) {
+ registerFallbackRouter(plugin, groupName, event -> true);
+ }
+
+ public static void registerFallbackRouter(@NonNull Object plugin, @NonNull String groupName, @NonNull Predicate filter) {
+ Events.listen(KickedFromServerEvent.class, event -> {
+ if (!filter.test(event)) return;
+ String kickedFrom = event.getServer().getServerInfo().getName();
+ List lobbies = SERVER_GROUPS.get(groupName.toLowerCase(Locale.ROOT));
+ if (lobbies == null || lobbies.isEmpty()) return;
+
+ List filteredLobbies = lobbies.stream()
+ .filter(name -> !name.equalsIgnoreCase(kickedFrom))
+ .toList();
+
+ if (filteredLobbies.isEmpty()) return;
+
+ for (String lobbyName : filteredLobbies) {
+ var optionalServer = OumLib.proxy().getServer(lobbyName);
+ if (optionalServer.isPresent()) {
+ event.setResult(KickedFromServerEvent.RedirectPlayer.create(
+ optionalServer.get(),
+ event.getServerKickReason().orElse(null)
+ ));
+ return;
+ }
+ }
+ });
+ }
+
+ public static boolean sendPluginMessage(@NonNull Player player, @NonNull String channelName, @NonNull Consumer writer) {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ writer.accept(out);
+ return sendPluginMessage(player, channelName, out.toByteArray());
+ }
+
+ public static void broadcastPluginMessage(@NonNull String channelName, byte[] data) {
+ MinecraftChannelIdentifier identifier = MinecraftChannelIdentifier.from(channelName);
+ OumLib.proxy().getChannelRegistrar().register(identifier);
+ for (RegisteredServer server : OumLib.proxy().getAllServers()) {
+ server.sendPluginMessage(identifier, data);
+ }
+ }
+
+ public static void broadcastPluginMessage(@NonNull String channelName, @NonNull Consumer writer) {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ writer.accept(out);
+ broadcastPluginMessage(channelName, out.toByteArray());
+ }
+
+ public static @NonNull Promise ping(@NonNull String serverName) {
+ return Promise.fromCompletableFuture(
+ OumLib.proxy().getServer(serverName)
+ .map(server -> server.ping()
+ .thenApply(ping -> {
+ String motd = ping.getDescriptionComponent().toString();
+ String ver = ping.getVersion() != null ? ping.getVersion().getName() : null;
+ int online = ping.getPlayers().isPresent() ? ping.getPlayers().get().getOnline() : 0;
+ int max = ping.getPlayers().isPresent() ? ping.getPlayers().get().getMax() : 0;
+ return new ServerPingResult(true, online, max, motd, ver);
+ })
+ .exceptionally(err -> new ServerPingResult(false, 0, 0, null, null)))
+ .orElseGet(() -> CompletableFuture.completedFuture(new ServerPingResult(false, 0, 0, null, null)))
+ );
+ }
+
+ public static @NonNull Optional getPlayer(@NonNull String name) {
+ return OumLib.proxy().getPlayer(name);
+ }
+
+ public static @NonNull Optional getPlayer(@NonNull UUID uuid) {
+ return OumLib.proxy().getPlayer(uuid);
+ }
+
+ public static @NonNull Collection getPlayers() {
+ return OumLib.proxy().getAllPlayers();
+ }
+
+ public static @NonNull Collection getPlayersOn(@NonNull String serverName) {
+ return OumLib.proxy().getServer(serverName)
+ .map(RegisteredServer::getPlayersConnected)
+ .orElse(List.of());
+ }
+
+ public record ServerPingResult(
+ boolean online,
+ int currentPlayers,
+ int maxPlayers,
+ @Nullable String motd,
+ @Nullable String version
+ ) {
}
}
diff --git a/oumlib-core/src/main/java/dev/oum/oumlib/web/WebhookEmbedField.java b/oumlib-core/src/main/java/dev/oum/oumlib/web/WebhookEmbedField.java
index 183cc96..3860168 100644
--- a/oumlib-core/src/main/java/dev/oum/oumlib/web/WebhookEmbedField.java
+++ b/oumlib-core/src/main/java/dev/oum/oumlib/web/WebhookEmbedField.java
@@ -1,3 +1,4 @@
package dev.oum.oumlib.web;
-public record WebhookEmbedField(String name, String value, boolean inline) {}
+public record WebhookEmbedField(String name, String value, boolean inline) {
+}
diff --git a/oumlib-plugin/pom.xml b/oumlib-plugin/pom.xml
new file mode 100644
index 0000000..75744ec
--- /dev/null
+++ b/oumlib-plugin/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+
+ dev.oum
+ oumlib
+ 1.0.7
+ ../pom.xml
+
+
+ oumlib-plugin
+ jar
+
+
+
+ dev.oum
+ oumlib-core
+ 1.0.7
+ compile
+
+
+ io.papermc.paper
+ paper-api
+ 1.21.11-R0.1-SNAPSHOT
+ provided
+
+
+ com.velocitypowered
+ velocity-api
+ 3.5.0-SNAPSHOT
+ provided
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.2
+
+
+ package
+
+ shade
+
+
+ false
+ false
+
+
+
+
+
+
+
diff --git a/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/PaperOumLibPlugin.java b/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/PaperOumLibPlugin.java
new file mode 100644
index 0000000..9dc5b35
--- /dev/null
+++ b/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/PaperOumLibPlugin.java
@@ -0,0 +1,17 @@
+package dev.oum.oumlib.plugin;
+
+import dev.oum.oumlib.OumLib;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public final class PaperOumLibPlugin extends JavaPlugin {
+
+ @Override
+ public void onEnable() {
+ OumLib.init(this);
+ }
+
+ @Override
+ public void onDisable() {
+ OumLib.shutdown();
+ }
+}
diff --git a/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/VelocityOumLibPlugin.java b/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/VelocityOumLibPlugin.java
new file mode 100644
index 0000000..4b1699b
--- /dev/null
+++ b/oumlib-plugin/src/main/java/dev/oum/oumlib/plugin/VelocityOumLibPlugin.java
@@ -0,0 +1,30 @@
+package dev.oum.oumlib.plugin;
+
+import com.google.inject.Inject;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
+import com.velocitypowered.api.plugin.Plugin;
+import com.velocitypowered.api.proxy.ProxyServer;
+import dev.oum.oumlib.OumLib;
+
+@Plugin(id = "oumlib", name = "OumLib", version = "1.0.7", description = "Provided plugin library for OumLib.")
+public final class VelocityOumLibPlugin {
+
+ private final ProxyServer server;
+
+ @Inject
+ public VelocityOumLibPlugin(ProxyServer server) {
+ this.server = server;
+ }
+
+ @Subscribe
+ public void onProxyInitialization(ProxyInitializeEvent event) {
+ OumLib.init(server, this);
+ }
+
+ @Subscribe
+ public void onProxyShutdown(ProxyShutdownEvent event) {
+ OumLib.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/oumlib-plugin/src/main/resources/paper-plugin.yml b/oumlib-plugin/src/main/resources/paper-plugin.yml
new file mode 100644
index 0000000..7abfd4f
--- /dev/null
+++ b/oumlib-plugin/src/main/resources/paper-plugin.yml
@@ -0,0 +1,6 @@
+name: OumLib
+version: 1.0.7
+main: dev.oum.oumlib.plugin.PaperOumLibPlugin
+api-version: '1.21.4'
+author: sun-mc-dev
+description: Provided plugin library for OumLib.
diff --git a/pom.xml b/pom.xml
index 872f04b..03db624 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,11 +7,12 @@
dev.oum
oumlib
- 1.0.6
+ 1.0.7
pom
oumlib-core
+ oumlib-plugin
example-plugin