Author SHA1 Message Date
NotAlexNoyle 3c2e10c103 Point submodules at canonical Gitea repositories 2026-11-01 12:48:12 -05:00
NotAlexNoyle aeb29a7dc4 Update canonical Gitea links 2026-10-29 09:17:54 -04:00
NotAlexNoyle e7449ad436 Update canonical Gitea links 2026-10-29 09:17:53 -04:00
NotAlexNoyle fa87728993 Exclude CODEOWNERS from language statistics 2026-10-28 15:32:12 -04:00
NotAlexNoyle 0f9cd07dab Add CODEOWNERS (one staff approval) 2026-10-28 15:32:12 -04:00
NotAlexNoyle c0b7ec3dfa Update Utilities-OG 2026-10-18 15:06:05 -04:00
NotAlexNoyle 08f2b85274 Update Utilities-OG 2026-09-14 02:04:49 -04:00
NotAlexNoyle da395ca2e0 v1.2.0 release 2026-09-12 09:41:36 -04:00
NotAlexNoyle 1a61dbc537 v1.1.0 update 2026-07-29 11:16:13 -04:00
NotAlexNoyle aeed0fbbd5 Blue diamond count 2026-07-20 12:07:12 -04:00
NotAlexNoyle ab01e7071f Add dedicated diamond balance line 2026-07-20 02:39:05 -04:00
NotAlexNoyle cada1a81d6 Update Utilities-OG API 2026-07-20 00:58:40 -04:00
NotAlexNoyle 0835cb0d9e Update deaths placeholder 2026-06-18 15:01:33 -04:00
NotAlexNoyle 1caa0f12f5 Update Utilities-OG API 2026-05-20 11:48:29 -04:00
NotAlexNoyle e859f25729 Update Utilities-OG 2026-05-11 00:59:39 -04:00
NotAlexNoyle 146770ccbc Fix placeholders 2026-05-04 19:06:27 -04:00
NotAlexNoyle d1de3a950e Scoreboard-Library 2.6.0 2026-04-27 15:41:38 -04:00
NotAlexNoyle 52ebddcad5 Update placeholders 2026-04-24 11:56:10 -04:00
NotAlexNoyle eec897db46 Hide scoreboard numbers on 1.20.3+ without breakin TAB belowname 2026-04-24 11:04:34 -04:00
NotAlexNoyle 7719a3e9c0 Update Utilities-OG API 2026-04-20 02:38:51 -04:00
NotAlexNoyle 5ec8c231d4 Remove unused Chat-OG API 2026-04-06 03:10:26 -04:00
NotAlexNoyle eb5a600b4d Update Chat-OG, Utilities-OG APIs 2026-04-05 13:47:35 -04:00
NotAlexNoyle ae262ae9ff Update DiamondBank-OG API 2026-04-04 14:12:49 -04:00
NotAlexNoyle f74a5160a5 Update Chat-OG API 2026-04-01 05:04:55 -04:00
NotAlexNoyle 06d6f6f504 Add /togglescoreboard 2026-04-01 04:48:39 -04:00
NotAlexNoyle d7a9ee7033 Update Utilities-OG API 2026-03-30 05:13:17 -04:00
NotAlexNoyle ad218d0c7b Update Chat-OG API 2026-03-25 14:55:05 -04:00
NotAlexNoyle 277035f994 Update DiamondBank-OG to MiniPlaceholder 2026-03-25 13:50:27 -04:00
NotAlexNoyle 22378436cb Update Chat-OG API 2026-03-25 13:04:10 -04:00
NotAlexNoyle 1f25bd8fe3 Update Utilities-OG API 2026-03-25 12:01:01 -04:00
NotAlexNoyle 8028bda73c Fix placeholders 2026-03-25 10:26:16 -04:00
NotAlexNoyle a54efe2c06 Update scoreboard-library to v2.5.0 2026-03-25 06:10:44 -04:00
NotAlexNoyle 8cc7b17842 Stop using DiamondBank-OG API 2026-03-11 00:31:43 -04:00
NotAlexNoyle 8eb806f091 Update Chat-OG API 2026-03-09 14:00:58 -04:00
NotAlexNoyle 29c114e4b6 Update DiamondBank-OG API 2026-03-02 06:44:59 -05:00
NotAlexNoyle 04945bedbb Update DiamondBank-OG API 2026-01-24 10:49:31 -05:00
NotAlexNoyle 8ca62066b9 Update DiamondBank-OG API 2026-01-24 10:39:49 -05:00
NotAlexNoyle 1f3d04eb8c Update Chat-OG and DiamondBank-OG APIs 2026-01-19 13:34:35 -05:00
NotAlexNoyle 819daf396f Update Chat-OG and Utilities-OG APIs 2025-12-13 12:08:59 -05:00
NotAlexNoyle fe56b43f32 Update README.md 2025-12-12 00:23:16 -05:00
NotAlexNoyle 0c5e6ebc3f Update Scoreboard Library 2025-12-11 02:20:11 -05:00
NotAlexNoyle 69789ee7ba v1.0 Release 2025-12-11 02:18:06 -05:00
NotAlexNoyle 3e27812b4a Merge pull request #6 from true-og/codex/fix-scoreboard-packet-adapter-error
Preserve scoreboard library adapters in shadow jar
2025-12-11 00:42:10 -05:00
NotAlexNoyle 2ae797371a Preserve scoreboard library adapters in shadow jar 2025-12-11 00:41:58 -05:00
NotAlexNoyle 972d58f5c8 New library 2025-12-11 00:26:02 -05:00
15 changed files with 698 additions and 91 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+1 -7
View File
@@ -1,9 +1,3 @@
[submodule "libs/Utilities-OG"]
path = libs/Utilities-OG
url = https://github.com/true-og/Utilities-OG
[submodule "libs/DiamondBank-OG"]
path = libs/DiamondBank-OG
url = https://github.com/true-og/DiamondBank-OG
[submodule "libs/Chat-OG"]
path = libs/Chat-OG
url = https://github.com/true-og/Chat-OG
url = https://git.trueog.net/trueog/Utilities-OG
+42
View File
@@ -0,0 +1,42 @@
# Changelog
All notable changes to Scoreboard-OG are documented here.
## 1.2.0 - 2026-09-12
### Changes
- Add a sidebar API for other plugins: `ScoreboardOGAPI.claim`, `release`,
`releaseAll` and `getOwner`, with a `SidebarProvider` supplying a title and up
to 15 lines. A claim replaces the network board with the provider's content,
survives `/togglescoreboard` and world changes, and hands the sidebar back on
release, quit, or when the provider throws.
## 1.1.0 - 2026-07-29
### Changes
- Restore the network sidebar after players return from minigames by refreshing
it one tick after world transitions when no Bukkit scoreboard remains active.
- Add `/togglescoreboard` with persistent per-player visibility preferences.
- Add a dedicated aqua diamond-balance line.
- Correct the SimpleClans union, BattleTracker PvP kill, and BattleTracker PvP
death placeholders, and migrate DiamondBank balances to MiniPlaceholder
expansion.
- Upgrade the packet-based sidebar implementation to scoreboard-library 2.6.0
and preserve its runtime adapters in shaded builds.
- Update the DiamondBank-OG and Utilities-OG integrations and remove the unused
Chat-OG dependency.
## 1.0.0 - 2025-12-11
### Changes
- Release a player-specific TrueOG Network sidebar for the Purpur 1.19.4
server target.
- Display each player's LuckPerms rank and colored name, SimpleClans union,
diamond balance, kills, deaths, and the network address.
- Refresh dynamic sidebar values every ten ticks and manage boards across
player joins, quits, plugin reloads, and shutdowns.
- Package the packet-backed scoreboard implementation and its required runtime
adapter in the shaded plugin JAR.
+26 -2
View File
@@ -1,6 +1,8 @@
# Scoreboard-OG
TrueOG Network's custom Scoreboard plugin supporting dynamic MiniPlaceholders. Based on the [ScoreboardLib](https://github.com/TigerHix/ScoreboardLib) API by TigerHix.
TrueOG Network's custom Scoreboard plugin supporting dynamic MiniPlaceholders. Based on [scoreboard-library by MegavexNetwork](https://github.com/MegavexNetwork/scoreboard-library).
Current version: `1.2.0` ([changelog](CHANGELOG.md)).
Gradle Version: 8.14.3
@@ -10,4 +12,26 @@ Minecraft API: 1.19
API Jar: 1.19.4 Purpur
Scoreboard-OG is public domain software released under the terms of [The Unlicense](https://github.com/true-og/Template-OG/blob/main/LICENSE).
## API for other plugins
A minigame can take over a player's sidebar for the length of a match and hand it back afterwards. Compile against the jar, soft-depend on `Scoreboard-OG`, and only touch the API after checking the plugin is enabled.
```java
import plugin.api.ScoreboardOGAPI;
import plugin.api.SidebarProvider;
ScoreboardOGAPI.claim(player, "MyGame-OG", new SidebarProvider() {
public Component title(Player player) { return Component.text("My Game"); }
public List<Component> lines(Player player) { return List.of(Component.text("Players: 4")); }
});
// later
ScoreboardOGAPI.release(player, "MyGame-OG");
// on disable
ScoreboardOGAPI.releaseAll("MyGame-OG");
```
`claim` replaces the network board with the provider's content, polled on the main thread every ten ticks; up to 15 lines, fewer lines shrink the board. `release` hands the sidebar back only when the named owner still holds it, and the network board returns on its own. A player who hid the sidebar with `/togglescoreboard` sees neither board. A provider that throws loses its claim.
TheHerobrine-OG, BuildBattle-OG and Splegg-OG use this API and fall back to their own scoreboards when Scoreboard-OG is absent.
Scoreboard-OG is public domain software released under the terms of [The Unlicense](https://git.trueog.net/trueog/Template-OG/src/branch/main/LICENSE).
+21 -7
View File
@@ -30,10 +30,12 @@ kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "net.trueog.scoreboard-og" // Declare bundle identifier.
version = "1.0" // Declare plugin version (will be in .jar).
version = "1.2.0" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
val scoreboardLibraryVersion = "2.6.0" // Declare vytskalt/scoreboard-library version.
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
@@ -47,7 +49,9 @@ repositories {
mavenCentral() // Import the Maven Central Maven Repository.
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
maven { url = uri("https://repo.hotten.cloud/snapshots") } // Import the ScoreboardLib library.
maven { url = uri("https://repo.codemc.io/repository/maven-releases/") }
maven { url = uri("https://repo.codemc.io/repository/maven-snapshots/") }
maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
val dir = file(it)
@@ -65,12 +69,16 @@ repositories {
dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API.
compileOnly("me.clip:placeholderapi:2.11.6") // Import PlaceholderAPI for direct sidebar expansion.
compileOnly("net.luckperms:api:5.5") // Import LuckPerms API.
compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source).
compileOnlyApi(project(":libs:DiamondBank-OG")) {
attributes { attribute(kotlinAttribute, true) }
} // Import TrueOG network DiamondBank-OG Kotlin API (from source).
implementation("me.tigerhix.lib:scoreboard:1.0.1-SNAPSHOT")
implementation("net.megavex:scoreboard-library-api:$scoreboardLibraryVersion") // Import scoreboard library API.
runtimeOnly(
"net.megavex:scoreboard-library-implementation:$scoreboardLibraryVersion"
) // Import scoreboard library implementation.
implementation(
"net.megavex:scoreboard-library-extra-kotlin:$scoreboardLibraryVersion"
) // Kotlin specific extensions.
}
apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script.
@@ -85,7 +93,13 @@ tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .ja
tasks.shadowJar {
exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed.
archiveClassifier.set("") // Use empty string instead of null.
minimize()
isEnableRelocation = true
relocationPrefix = "${project.group}.shadow"
minimize {
exclude(dependency("net.megavex:scoreboard-library-api:.*"))
exclude(dependency("net.megavex:scoreboard-library-extra-kotlin:.*"))
exclude(dependency("net.megavex:scoreboard-library-implementation:.*"))
}
}
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
Submodule libs/Chat-OG deleted from ca8cbae719.
+31
View File
@@ -2,11 +2,15 @@
// Author: NotAlexNoyle.
package plugin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scoreboard.ScoreboardManager;
public class Listeners implements Listener {
@@ -21,6 +25,33 @@ public class Listeners implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) {
ScoreboardOG.getInstance().closeBoard(event.getPlayer());
ScoreboardOG.getInstance().forgetClaims(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
final ScoreboardOG plugin = ScoreboardOG.getInstance();
final Player player = event.getPlayer();
// Minigames can replace the sidebar with a Bukkit scoreboard. Wait until
// every world-change listener has finished, then reclaim the sidebar only
// when no plugin still owns the player's Bukkit scoreboard.
Bukkit.getScheduler().runTask(plugin, () -> {
final ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
if (!player.isOnline() || scoreboardManager == null
|| player.getScoreboard() != scoreboardManager.getMainScoreboard())
{
return;
}
plugin.openBoard(player);
});
}
+395 -71
View File
@@ -2,6 +2,8 @@
// Author: NotAlexNoyle.
package plugin;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -9,28 +11,47 @@ import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import me.tigerhix.lib.scoreboard.ScoreboardLib;
import me.tigerhix.lib.scoreboard.common.EntryBuilder;
import me.tigerhix.lib.scoreboard.type.Entry;
import me.tigerhix.lib.scoreboard.type.Scoreboard;
import me.tigerhix.lib.scoreboard.type.ScoreboardHandler;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.model.user.User;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.trueog.utilitiesog.UtilitiesOG;
import plugin.api.SidebarProvider;
public class ScoreboardOG extends JavaPlugin {
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacyAmpersand();
private static final Component EMPTY_LINE = Component.empty();
private static final Component SIDEBAR_TITLE = legacyText("&4♥ &a&lTrue&c&lOG&r&e Network &4♥");
private static final Component FOOTER_LINE = legacyText("&etrue-og.net");
private static final int NETWORK_LINE_COUNT = 14;
private static ScoreboardOG instance;
private FileConfiguration config;
private final Map<UUID, Scoreboard> boards = new HashMap<>();
private final Map<UUID, PlayerSidebar> sidebars = new HashMap<>();
// Sidebars other plugins currently own, kept apart from the boards so a toggle
// or a world change never drops a running match's content.
private final Map<UUID, SidebarClaim> claims = new HashMap<>();
private record SidebarClaim(String owner, SidebarProvider provider) {
}
private LuckPerms luckPerms;
private ScoreboardLibrary scoreboardLibrary;
private int updateTaskId = -1;
private File scoreboardPreferencesFile;
private YamlConfiguration scoreboardPreferences;
public static ScoreboardOG getInstance() {
@@ -50,7 +71,35 @@ public class ScoreboardOG extends JavaPlugin {
saveDefaultConfig();
this.config = getConfig();
ScoreboardLib.setPluginInstance(this);
this.scoreboardPreferencesFile = new File(getDataFolder(), "scoreboardPreferences.yml");
if (!scoreboardPreferencesFile.exists()) {
try {
getDataFolder().mkdirs();
scoreboardPreferencesFile.createNewFile();
} catch (IOException error) {
throw new RuntimeException(error);
}
}
this.scoreboardPreferences = YamlConfiguration.loadConfiguration(scoreboardPreferencesFile);
try {
this.scoreboardLibrary = ScoreboardLibrary.loadScoreboardLibrary(this);
} catch (Exception e) {
this.scoreboardLibrary = null;
getLogger().warning("No scoreboard packet adapter available, disabling scoreboards.");
}
// Hook LuckPerms API.
final RegisteredServiceProvider<LuckPerms> provider = Bukkit.getServicesManager()
@@ -66,15 +115,43 @@ public class ScoreboardOG extends JavaPlugin {
}
getServer().getPluginManager().registerEvents(new Listeners(), this);
getCommand("togglescoreboard").setExecutor(new ToggleScoreboardCommand());
Bukkit.getOnlinePlayers().forEach(this::openBoard);
this.updateTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(this,
() -> Bukkit.getOnlinePlayers().forEach((Player online) ->
{
final PlayerSidebar sidebar = sidebars.get(online.getUniqueId());
if (sidebar != null) {
sidebar.tick();
}
}), 0L, 10L);
}
@Override
public void onDisable() {
boards.values().forEach(Scoreboard::deactivate);
boards.clear();
if (updateTaskId != -1) {
Bukkit.getScheduler().cancelTask(updateTaskId);
updateTaskId = -1;
}
sidebars.values().forEach(PlayerSidebar::close);
sidebars.clear();
claims.clear();
if (scoreboardLibrary != null) {
scoreboardLibrary.close();
}
}
@@ -84,57 +161,193 @@ public class ScoreboardOG extends JavaPlugin {
}
public boolean isSidebarReady() {
return scoreboardLibrary != null;
}
public void claimSidebar(Player player, String owner, SidebarProvider provider) {
claims.put(player.getUniqueId(), new SidebarClaim(owner, provider));
final PlayerSidebar sidebar = sidebars.get(player.getUniqueId());
if (sidebar != null) {
sidebar.tick();
}
}
public boolean releaseSidebar(Player player, String owner) {
final SidebarClaim claim = claims.get(player.getUniqueId());
if (claim == null || !claim.owner().equals(owner)) {
return false;
}
claims.remove(player.getUniqueId());
final PlayerSidebar sidebar = sidebars.get(player.getUniqueId());
if (sidebar != null) {
sidebar.tick();
}
return true;
}
public void releaseAllSidebars(String owner) {
for (Player online : Bukkit.getOnlinePlayers()) {
releaseSidebar(online, owner);
}
claims.values().removeIf(claim -> claim.owner().equals(owner));
}
public String getSidebarOwner(Player player) {
final SidebarClaim claim = claims.get(player.getUniqueId());
return claim == null ? null : claim.owner();
}
public void forgetClaims(Player player) {
claims.remove(player.getUniqueId());
}
public YamlConfiguration getScoreboardPreferences() {
return scoreboardPreferences;
}
public File getScoreboardPreferencesFile() {
return scoreboardPreferencesFile;
}
public boolean isScoreboardHidden(Player player) {
return scoreboardPreferences.getBoolean(player.getUniqueId().toString());
}
public void openBoard(Player player) {
final Scoreboard board = ScoreboardLib.createScoreboard(player).setHandler(new ScoreboardHandler() {
if (scoreboardLibrary == null) {
@Override
public String getTitle(Player p) {
return;
return "&4♥ &a&lTrue&c&lOG&r&e Network &4♥";
}
}
if (isScoreboardHidden(player)) {
@Override
public java.util.List<Entry> getEntries(Player p) {
return;
// Fetch LuckPerms prefix with formatting.
String lpPrefix = getLuckPermsPrefixLegacy(p);
lpPrefix = stripLeadingReset(stripTrailingReset(lpPrefix));
}
// Bleed formatting from prefix into name.
final String youTail = joinSpace(lpPrefix, p.getName());
final String youLine = "&aYou:&r " + youTail + "&r";
closeBoard(player);
// Independently formatted Union tag.
final String unionTag = expandMM(p, "<placeholderapi_player:%simpleclans_clan_color_tag%>");
final String unionLine = "&cUnion: &r" + unionTag + "&r";
return new EntryBuilder().next("&c&m----&6&m----&e&m----&2&m----&9&m----&5&m----").next(youLine).blank()
.next("&bDiamonds: " + expandMM(p, "<diamondbankog_balance>")).blank().next(unionLine).blank()
.next("&2Kills: " + expandMM(p, "<placeholderapi_player:%statistic_player_kills%>")).blank()
.next("&4Deaths: " + expandMM(p, "<placeholderapi_player:%statistic_deaths%>")).blank()
.next("&4&m--&0&m--&4> &etrue-og.net &4<&0&m--&4&m--").build();
}
}).setUpdateInterval(10L);
board.activate();
boards.put(player.getUniqueId(), board);
final PlayerSidebar sidebar = new PlayerSidebar(player);
sidebars.put(player.getUniqueId(), sidebar);
}
public void closeBoard(Player player) {
final Scoreboard board = boards.remove(player.getUniqueId());
if (board != null) {
final PlayerSidebar sidebar = sidebars.remove(player.getUniqueId());
if (sidebar != null) {
board.deactivate();
sidebar.close();
}
}
private Component createRankLine(Player p) {
String lpPrefix = getLuckPermsPrefixLegacy(p);
lpPrefix = stripLeadingReset(stripTrailingReset(lpPrefix));
return legacyText(lpPrefix);
}
private Component createYouLine(Player p) {
String lpPrefix = getLuckPermsPrefixLegacy(p);
lpPrefix = stripLeadingReset(stripTrailingReset(lpPrefix));
final String colorCodes = extractLeadingColorCodes(lpPrefix);
if (StringUtils.isEmpty(colorCodes)) {
return legacyText(p.getName());
}
return legacyText(colorCodes + p.getName());
}
private Component createUnionLine(Player p) {
final Component label = legacyText("&cUnion: &r");
final Component unionTag = expandText(p, "<placeholderapi_player:%simpleclans_union_color_tag%>");
return label.append(unionTag);
}
private Component createDiamondsLabelLine() {
return legacyText("&bDiamonds:");
}
private Component createDiamondsValueLine(Player p) {
return expandText(p, "<color:aqua><diamondbankog_balance>");
}
private Component createKillsLine(Player p) {
final Component label = legacyText("&2Kills: &r");
final Component value = expandText(p, "<placeholderapi_player:%bt_pvp_kills%>");
return label.append(value);
}
private Component createDeathsLine(Player p) {
final Component label = legacyText("&4Deaths: &r");
final Component value = expandText(p, "<placeholderapi_player:%bt_pvp_deaths%>");
return label.append(value);
}
private static Component legacyText(String input) {
if (input == null || StringUtils.isEmpty(input)) {
return Component.empty();
}
return LEGACY_SERIALIZER.deserialize(input);
}
// Resolve the player's LuckPerms prefix as legacy color codes with any
// serializing.
private String getLuckPermsPrefixLegacy(Player p) {
@@ -169,11 +382,20 @@ public class ScoreboardOG extends JavaPlugin {
}
/// Return expanded text or null depending on if the String is populated.
private static String expandMM(Player p, String miniMsg) {
// Return expanded text or null depending on if the String is populated.
private static Component expandText(Player p, String text) {
final String out = UtilitiesOG.trueogExpand(miniMsg, p).content();
return out == null ? "" : out;
if (text == null || StringUtils.isEmpty(text)) {
return Component.empty();
}
// Expand MiniPlaceholders and format the message.
final Component out = UtilitiesOG.trueogExpand(text, p);
// Pass on the message.
return out == null ? Component.empty() : out;
}
@@ -203,39 +425,141 @@ public class ScoreboardOG extends JavaPlugin {
}
// Ensure the prefix and name are spaced out.
private static String joinSpace(String... parts) {
private final class PlayerSidebar {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (String part : parts) {
private final Player player;
private final Sidebar sidebar;
private int renderedLines = 0;
if (part == null) {
private PlayerSidebar(Player player) {
continue;
}
final String t = StringUtils.trim(part);
if (StringUtils.isEmpty(t)) {
continue;
}
if (!first) {
sb.append(' ');
}
sb.append(t);
first = false;
this.player = player;
this.sidebar = scoreboardLibrary.createSidebar();
this.sidebar.addPlayer(player);
}
return sb.toString();
private void tick() {
final SidebarClaim claim = claims.get(player.getUniqueId());
if (claim != null) {
tickClaim(claim);
return;
}
sidebar.title(SIDEBAR_TITLE);
shrinkTo(NETWORK_LINE_COUNT);
setLine(0, EMPTY_LINE);
setLine(1, createRankLine(player));
setLine(2, createYouLine(player));
setLine(3, EMPTY_LINE);
setLine(4, createDiamondsLabelLine());
setLine(5, createDiamondsValueLine(player));
setLine(6, EMPTY_LINE);
setLine(7, createUnionLine(player));
setLine(8, EMPTY_LINE);
setLine(9, createKillsLine(player));
setLine(10, EMPTY_LINE);
setLine(11, createDeathsLine(player));
setLine(12, EMPTY_LINE);
setLine(13, FOOTER_LINE);
renderedLines = NETWORK_LINE_COUNT;
}
// A provider that throws loses its claim, so one bad plugin cannot blank the
// board every ten ticks.
private void tickClaim(SidebarClaim claim) {
final Component title;
final java.util.List<Component> lines;
try {
title = claim.provider().title(player);
lines = claim.provider().lines(player);
} catch (Throwable error) {
getLogger().warning("Sidebar provider from " + claim.owner() + " failed for " + player.getName()
+ ", handing the sidebar back: " + error.getMessage());
claims.remove(player.getUniqueId());
return;
}
sidebar.title(title == null ? Component.empty() : title);
final int count = lines == null ? 0 : Math.min(lines.size(), sidebar.maxLines());
shrinkTo(count);
for (int index = 0; index < count; index++) {
final Component line = lines.get(index);
setLine(index, line == null ? EMPTY_LINE : line);
}
renderedLines = count;
}
// The library keeps lines until they are cleared, so a shorter board has to
// drop the surplus before it is redrawn.
private void shrinkTo(int count) {
if (count < renderedLines) {
sidebar.clearLines();
}
}
private void close() {
sidebar.close();
}
private void setLine(int index, Component component) {
sidebar.line(index, component);
}
}
}
private String extractLeadingColorCodes(String input) {
if (input == null || input.length() < 2) {
return "";
}
final StringBuilder out = new StringBuilder();
for (int i = 0; i < input.length() - 1; i++) {
final char c = input.charAt(i);
if (c != '&' && c != '§') {
break;
}
final char code = input.charAt(i + 1);
out.append(c).append(code);
// Skip the code character.
i++;
}
return out.toString();
}
}
@@ -0,0 +1,76 @@
// This is free and unencumbered software released into the public domain.
// Author: NotAlexNoyle.
package plugin;
import java.io.IOException;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import net.trueog.utilitiesog.UtilitiesOG;
import net.trueog.utilitiesog.utils.TextUtils;
public class ToggleScoreboardCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
final String toggleScoreboardPermission = "scoreboard.toggle";
if (sender instanceof Player player) {
if (player.hasPermission(toggleScoreboardPermission)) {
final ScoreboardOG plugin = ScoreboardOG.getInstance();
final YamlConfiguration preferences = plugin.getScoreboardPreferences();
final boolean currentlyHidden = preferences.getBoolean(player.getUniqueId().toString());
preferences.set(player.getUniqueId().toString(), !currentlyHidden);
if (currentlyHidden) {
plugin.openBoard(player);
UtilitiesOG.trueogMessage(player,
"<#AAAAAA>[<#00AA00>Scoreboard<#AA0000>-OG<#AAAAAA>] <#55FF55>Scoreboard turned <#00AA00>ON<#55FF55>.");
} else {
plugin.closeBoard(player);
UtilitiesOG.trueogMessage(player,
"<#AAAAAA>[<#00AA00>Scoreboard<#AA0000>-OG<#AAAAAA>] <#FFAA00>Scoreboard turned <#FF5555>OFF<#FFAA00>.");
}
try {
preferences.save(plugin.getScoreboardPreferencesFile());
} catch (IOException error) {
throw new RuntimeException(error);
}
} else {
TextUtils.permissionsErrorMessage(player, cmd.getName(), toggleScoreboardPermission);
}
} else {
TextUtils.logToConsole(cmd.getName() + " " + toggleScoreboardPermission);
}
return true;
}
}
@@ -0,0 +1,67 @@
// This is free and unencumbered software released into the public domain.
// Author: NotAlexNoyle.
package plugin.api;
import org.bukkit.entity.Player;
import plugin.ScoreboardOG;
// Lets a minigame take over a player's sidebar for the length of a match and hand
// it back afterwards. The network board returns on its own once the claim is released.
public final class ScoreboardOGAPI {
private ScoreboardOGAPI() {
}
// True once Scoreboard-OG is enabled and its packet sidebar is usable.
public static boolean isAvailable() {
final ScoreboardOG plugin = ScoreboardOG.getInstance();
return plugin != null && plugin.isEnabled() && plugin.isSidebarReady();
}
// Replaces the network board with the provider's content. A later claim by
// any owner wins; the owner name is what release checks.
public static void claim(Player player, String owner, SidebarProvider provider) {
if (!isAvailable() || player == null || owner == null || provider == null)
return;
ScoreboardOG.getInstance().claimSidebar(player, owner, provider);
}
// Hands the sidebar back if this owner still holds it. Returns whether it did.
public static boolean release(Player player, String owner) {
if (!isAvailable() || player == null || owner == null)
return false;
return ScoreboardOG.getInstance().releaseSidebar(player, owner);
}
// Hands back every sidebar this owner holds; for a plugin's onDisable.
public static void releaseAll(String owner) {
if (!isAvailable() || owner == null)
return;
ScoreboardOG.getInstance().releaseAllSidebars(owner);
}
// The owner currently holding the player's sidebar, or null for the network
// board.
public static String getOwner(Player player) {
if (!isAvailable() || player == null)
return null;
return ScoreboardOG.getInstance().getSidebarOwner(player);
}
}
@@ -0,0 +1,20 @@
// This is free and unencumbered software released into the public domain.
// Author: NotAlexNoyle.
package plugin.api;
import java.util.List;
import org.bukkit.entity.Player;
import net.kyori.adventure.text.Component;
// Content another plugin wants on a player's sidebar instead of the network board.
// Both methods are polled on the main thread every ten ticks while the claim holds.
public interface SidebarProvider {
Component title(Player player);
// Top to bottom, at most 15 entries. Fewer entries shrink the sidebar.
List<Component> lines(Player player);
}
+14 -1
View File
@@ -1,7 +1,7 @@
name: Scoreboard-OG
author: NotAlexNoyle
description: A simple Scoreboard plugin with MiniPlaceholder API support.
website: https://github.com/true-og/Scoreboard-OG/
website: https://git.trueog.net/trueog/Scoreboard-OG/
version: '${version}'
api-version: '${apiVersion}'
main: plugin.ScoreboardOG
@@ -9,5 +9,18 @@ depend:
- DiamondBank-OG
- Utilities-OG
- LuckPerms
- ViaVersion
- ViaBackwards
- ViaRewind
softdepend:
- PlaceholderAPI
- ProtocolLib
commands:
togglescoreboard:
description: Toggle the sidebar scoreboard on or off.
usage: /togglescoreboard
permission: scoreboard.toggle
permissions:
scoreboard.toggle:
description: Allows a player to toggle their scoreboard visibility.
default: true