From 0f13c6541590aa7160a88d5b0b7dcb71ffa09731 Mon Sep 17 00:00:00 2001 From: wullie Date: Sun, 30 Nov 2025 06:53:55 +0000 Subject: [PATCH] Upload files to "src/main/java/com/wulliestudio/tab" --- .../java/com/wulliestudio/tab/TabPlugin.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/main/java/com/wulliestudio/tab/TabPlugin.java diff --git a/src/main/java/com/wulliestudio/tab/TabPlugin.java b/src/main/java/com/wulliestudio/tab/TabPlugin.java new file mode 100644 index 0000000..3f2d78c --- /dev/null +++ b/src/main/java/com/wulliestudio/tab/TabPlugin.java @@ -0,0 +1,108 @@ +package com.wulliestudio.tab; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class TabPlugin extends JavaPlugin { + + private final DecimalFormat tpsFormat = new DecimalFormat("#0.0"); + private long enableMillis; + + @Override + public void onEnable() { + saveDefaultConfig(); + + // record plugin enable time (server uptime from onEnable) + this.enableMillis = System.currentTimeMillis(); + + long interval = Math.max(1L, getConfig().getLong("update-interval-ticks", 20L)); + + // Folia global region scheduler for UI updates (safe for TAB) + Bukkit.getGlobalRegionScheduler().runAtFixedRate( + this, + task -> updateAllPlayersTab(), + 1L, + interval + ); + + getLogger().info("FoliaTab enabled (Folia-safe). Update interval: " + interval + " ticks"); + } + + @Override + public void onDisable() { + getLogger().info("FoliaTab disabled."); + } + + private void updateAllPlayersTab() { + double tps = getTPS(); + String tpsFormatted = tpsFormat.format(tps); + + List headerLines = getConfig().getStringList("header"); + List footerLines = getConfig().getStringList("footer"); + String serverName = getConfig().getString("server-name", "Minecraft Server"); + + for (Player player : Bukkit.getOnlinePlayers()) { + String headerRaw = String.join("\n", headerLines); + String footerRaw = String.join("\n", footerLines); + + headerRaw = applyPlaceholders(headerRaw, player, serverName, tpsFormatted); + footerRaw = applyPlaceholders(footerRaw, player, serverName, tpsFormatted); + + Component header = LegacyComponentSerializer.legacyAmpersand().deserialize(headerRaw); + Component footer = LegacyComponentSerializer.legacyAmpersand().deserialize(footerRaw); + + // Send header/footer (Folia-safe from global region scheduler) + try { + player.sendPlayerListHeaderAndFooter(header, footer); + } catch (NoSuchMethodError e) { + // Fallback to separate methods if available + try { + player.sendPlayerListHeader(header); + player.sendPlayerListFooter(footer); + } catch (Throwable t) { + getLogger().warning("Failed to set header/footer for " + player.getName() + ": " + t.getMessage()); + } + } catch (Throwable t) { + getLogger().warning("Failed to set header/footer for " + player.getName() + ": " + t.getMessage()); + } + } + } + + private String applyPlaceholders(String input, Player player, String serverName, String tps) { + return input + .replace("%server_name%", serverName) + .replace("%tps%", tps) + .replace("%ping%", String.valueOf(player.getPing())) + .replace("%player_name%", player.getName()) + .replace("%uptime%", formatUptime(System.currentTimeMillis() - enableMillis)); + } + + private String formatUptime(long millis) { + long seconds = TimeUnit.MILLISECONDS.toSeconds(millis); + + long days = seconds / 86_400; + seconds %= 86_400; + long hours = seconds / 3_600; + seconds %= 3_600; + long minutes = seconds / 60; + seconds %= 60; + + return String.format("%02d:%02d:%02d:%02d", days, hours, minutes, seconds); + } + + private double getTPS() { + try { + double[] tpsArr = Bukkit.getServer().getTPS(); + if (tpsArr != null && tpsArr.length > 0) return tpsArr[0]; + } catch (Throwable ignored) { } + return 20.0; + } +} +