├── gradle.properties ├── assets └── sidebar.gif ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .editorconfig ├── api ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── api │ │ ├── objective │ │ ├── ObjectiveRenderType.java │ │ ├── ObjectiveScore.java │ │ ├── ObjectiveDisplaySlot.java │ │ ├── ScoreFormat.java │ │ └── ObjectiveManager.java │ │ ├── exception │ │ └── NoPacketAdapterAvailableException.java │ │ ├── team │ │ ├── enums │ │ │ ├── CollisionRule.java │ │ │ └── NameTagVisibility.java │ │ └── ScoreboardTeam.java │ │ ├── sidebar │ │ └── component │ │ │ ├── animation │ │ │ ├── SidebarAnimation.java │ │ │ ├── FramedSidebarAnimation.java │ │ │ └── CollectionSidebarAnimation.java │ │ │ ├── LineDrawable.java │ │ │ ├── ComponentSidebarLayout.java │ │ │ └── SidebarComponent.java │ │ └── noop │ │ ├── NoopObjectiveManager.java │ │ ├── NoopScoreboardLibrary.java │ │ ├── NoopScoreboardTeam.java │ │ ├── NoopScoreboardObjective.java │ │ ├── NoopSidebar.java │ │ ├── NoopTeamDisplay.java │ │ └── NoopTeamManager.java │ └── test │ └── java │ └── net │ └── megavex │ └── scoreboardlibrary │ └── api │ ├── animation │ └── CollectionAnimationTest.java │ └── sidebar │ └── component │ └── ComponentSidebarLayoutTest.java ├── commons ├── build.gradle.kts └── src │ └── main │ └── java │ └── net │ └── megavex │ └── scoreboardlibrary │ └── implementation │ └── commons │ ├── LineRenderingStrategy.java │ ├── LegacyFormatUtil.java │ ├── CollectionProvider.java │ └── LocaleProvider.java ├── renovate.json ├── implementation ├── src │ └── main │ │ └── java │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── implementation │ │ ├── scheduler │ │ ├── RunningTask.java │ │ ├── TaskScheduler.java │ │ ├── BukkitTaskScheduler.java │ │ └── FoliaTaskScheduler.java │ │ ├── player │ │ ├── PlayerDisplayable.java │ │ ├── ScoreboardLibraryPlayer.java │ │ ├── DisplayableQueue.java │ │ └── LocaleListener.java │ │ ├── sidebar │ │ ├── line │ │ │ ├── locale │ │ │ │ ├── LocaleLine.java │ │ │ │ ├── PostModernLocaleLine.java │ │ │ │ └── ModernLocaleLine.java │ │ │ ├── GlobalLineInfo.java │ │ │ ├── LocaleLineHandler.java │ │ │ └── SidebarLineHandler.java │ │ ├── SidebarUpdaterTask.java │ │ ├── SingleLocaleSidebar.java │ │ ├── SidebarTask.java │ │ └── PlayerDependantLocaleSidebar.java │ │ ├── team │ │ ├── TeamUpdaterTask.java │ │ └── TeamManagerTask.java │ │ ├── objective │ │ ├── ObjectiveUpdaterTask.java │ │ ├── ObjectiveManagerTask.java │ │ └── ScoreboardObjectiveImpl.java │ │ └── PacketAdapterLoader.java └── build.gradle.kts ├── versions ├── legacy │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── implementation │ │ └── packetAdapter │ │ └── legacy │ │ ├── LegacyMinecraftClasses.java │ │ ├── PacketAdapterProviderImpl.java │ │ ├── ChatColorUtil.java │ │ └── LegacyPacketSender.java ├── v1_8_R3 │ └── build.gradle.kts ├── packet-adapter-base │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── net │ │ │ └── megavex │ │ │ └── scoreboardlibrary │ │ │ └── implementation │ │ │ └── packetAdapter │ │ │ ├── PropertiesPacketType.java │ │ │ ├── team │ │ │ ├── EntriesPacketType.java │ │ │ ├── TeamDisplayPacketAdapter.java │ │ │ ├── TeamsPacketAdapter.java │ │ │ └── TeamConstants.java │ │ │ ├── util │ │ │ ├── reflect │ │ │ │ ├── PacketConstructor.java │ │ │ │ ├── MinecraftClasses.java │ │ │ │ ├── FieldAccessor.java │ │ │ │ └── ConstructorAccessor.java │ │ │ └── LocalePacketUtil.java │ │ │ ├── PacketSender.java │ │ │ ├── PacketAdapterProvider.java │ │ │ ├── objective │ │ │ ├── ObjectivePacketAdapter.java │ │ │ └── ObjectiveConstants.java │ │ │ └── ImmutableTeamProperties.java │ └── build.gradle.kts ├── packetevents │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── implementation │ │ └── packetAdapter │ │ └── packetevents │ │ ├── ScoreFormatConverter.java │ │ ├── team │ │ ├── LegacyTeamDisplayPacketAdapter.java │ │ ├── TeamsPacketAdapterImpl.java │ │ ├── AbstractTeamDisplayPacketAdapter.java │ │ ├── WrapperPlayServerTeamsLegacy.java │ │ └── AdventureTeamDisplayPacketAdapter.java │ │ ├── PacketAdapterProviderImpl.java │ │ └── PacketEventsSender.java └── modern │ ├── src │ └── main │ │ └── java │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── implementation │ │ └── packetAdapter │ │ └── modern │ │ ├── ComponentProvider.java │ │ ├── util │ │ ├── NativeAdventureUtil.java │ │ ├── RegistryUtil.java │ │ └── PacketUtil.java │ │ ├── objective │ │ ├── DisplaySlotProvider.java │ │ ├── SpigotObjectivePacketAdapter.java │ │ ├── PaperObjectivePacketAdapter.java │ │ └── ScoreFormatConverter.java │ │ ├── PacketAdapterProviderImpl.java │ │ ├── team │ │ ├── SpigotTeamsPacketAdapter.java │ │ ├── PaperTeamsPacketAdapterImpl.java │ │ └── AbstractTeamsPacketAdapterImpl.java │ │ └── ComponentProviderImpl.java │ └── build.gradle.kts ├── extra-kotlin ├── src │ └── main │ │ └── kotlin │ │ └── net │ │ └── megavex │ │ └── scoreboardlibrary │ │ └── extra │ │ └── kotlin │ │ ├── ComponentSidebarExtensions.kt │ │ ├── TeamManagerExtensions.kt │ │ ├── SidebarExtensions.kt │ │ ├── ScoreboardTeamExtensions.kt │ │ └── TeamDisplayExtensions.kt └── build.gradle.kts ├── flake.nix ├── .github └── workflows │ └── ci.yml ├── flake.lock ├── settings.gradle.kts ├── LICENSE ├── gradlew.bat └── INSTALLATION.md /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.parallel=true 3 | -------------------------------------------------------------------------------- /assets/sidebar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegavexNetwork/scoreboard-library/HEAD/assets/sidebar.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | 4 | .gradle 5 | build/ 6 | 7 | .classpath 8 | .project 9 | .settings/ 10 | bin/ 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegavexNetwork/scoreboard-library/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | indent_style = space 7 | insert_final_newline = true 8 | max_line_length = off -------------------------------------------------------------------------------- /api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | compileOnlyApi(libs.bundles.adventure) 7 | compileOnly(libs.spigotApi) 8 | } 9 | -------------------------------------------------------------------------------- /commons/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | api(project(":scoreboard-library-api")) 7 | compileOnly(libs.spigotApi) 8 | } 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "ignoreDeps": [ 7 | "org.spigotmc:spigot-api" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/scheduler/RunningTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.scheduler; 2 | 3 | public interface RunningTask { 4 | void cancel(); 5 | } 6 | -------------------------------------------------------------------------------- /versions/legacy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | compileOnly(project(":scoreboard-library-packet-adapter-base")) 7 | compileOnly(libs.spigotApi) 8 | } 9 | -------------------------------------------------------------------------------- /versions/v1_8_R3/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | // Alias to legacy for backwards compatibility 7 | implementation(project(":scoreboard-library-legacy")) 8 | } 9 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/PropertiesPacketType.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter; 2 | 3 | public enum PropertiesPacketType { 4 | CREATE, 5 | UPDATE 6 | } 7 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/team/EntriesPacketType.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.team; 2 | 3 | public enum EntriesPacketType { 4 | ADD, 5 | REMOVE 6 | } 7 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | api(project(":scoreboard-library-api")) 7 | api(project(":scoreboard-library-commons")) 8 | compileOnly(libs.spigotApi) 9 | } 10 | -------------------------------------------------------------------------------- /implementation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | dependencies { 6 | api(project(":scoreboard-library-api")) 7 | implementation(project(":scoreboard-library-packet-adapter-base")) 8 | compileOnly(libs.spigotApi) 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/player/PlayerDisplayable.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.player; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface PlayerDisplayable { 7 | void display(@NotNull Player player); 8 | } 9 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/util/reflect/PacketConstructor.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public interface PacketConstructor { 6 | @NotNull T invoke(); 7 | } 8 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/objective/ObjectiveRenderType.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.objective; 2 | 3 | /** 4 | * Represents all possible render types of an objective. 5 | * The render type represents the way scores are displayed. 6 | */ 7 | public enum ObjectiveRenderType { 8 | INTEGER, 9 | HEARTS 10 | } 11 | -------------------------------------------------------------------------------- /versions/packetevents/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | } 4 | 5 | repositories { 6 | maven("https://repo.codemc.io/repository/maven-releases/") 7 | } 8 | 9 | dependencies { 10 | compileOnly(project(":scoreboard-library-packet-adapter-base")) 11 | compileOnly(libs.packetEvents) 12 | compileOnly(libs.spigotApi) 13 | } 14 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/PacketSender.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | public interface PacketSender { 6 | void sendPacket(Player player, T packet); 7 | 8 | default void sendPacket(Iterable players, T packet) { 9 | for (Player player : players) { 10 | sendPacket(player, packet); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/exception/NoPacketAdapterAvailableException.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.exception; 2 | 3 | /** 4 | * Exception indicating that there is no packet adapter available for the current server version. 5 | * As a fallback, consider using the {@link net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary} implementation. 6 | */ 7 | public final class NoPacketAdapterAvailableException extends Exception { 8 | public NoPacketAdapterAvailableException() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/ComponentProvider.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Locale; 8 | 9 | public interface ComponentProvider { 10 | @NotNull net.minecraft.network.chat.Component fromAdventure(@NotNull Component adventure, @Nullable Locale locale); 11 | } 12 | -------------------------------------------------------------------------------- /extra-kotlin/src/main/kotlin/net/megavex/scoreboardlibrary/extra/kotlin/ComponentSidebarExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.extra.kotlin 2 | 3 | import net.megavex.scoreboardlibrary.api.sidebar.component.ComponentSidebarLayout 4 | import net.megavex.scoreboardlibrary.api.sidebar.component.SidebarComponent 5 | 6 | public inline val ComponentSidebarLayout.titleComponent: SidebarComponent 7 | get() = titleComponent() 8 | 9 | public inline val ComponentSidebarLayout.linesComponent: SidebarComponent 10 | get() = linesComponent() 11 | -------------------------------------------------------------------------------- /extra-kotlin/src/main/kotlin/net/megavex/scoreboardlibrary/extra/kotlin/TeamManagerExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.extra.kotlin 2 | 3 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam 4 | import net.megavex.scoreboardlibrary.api.team.TeamManager 5 | import org.bukkit.entity.Player 6 | 7 | public inline val TeamManager.teams: Collection get() = teams() 8 | public operator fun TeamManager.get(name: String): ScoreboardTeam? = team(name) 9 | public inline val TeamManager.players: Collection get() = players() 10 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/util/NativeAdventureUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util; 2 | 3 | import io.papermc.paper.adventure.AdventureComponent; 4 | import net.kyori.adventure.text.Component; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public final class NativeAdventureUtil { 8 | private NativeAdventureUtil() { 9 | } 10 | 11 | public static @NotNull AdventureComponent fromAdventureComponent(@NotNull Component component) { 12 | return new AdventureComponent(component); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/util/reflect/MinecraftClasses.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public final class MinecraftClasses { 7 | private static final String CB_PACKAGE = Bukkit.getServer().getClass().getPackage().getName(); 8 | 9 | private MinecraftClasses() { 10 | } 11 | 12 | public static @NotNull String craftBukkit(String className) { 13 | return CB_PACKAGE + '.' + className; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; 4 | }; 5 | 6 | outputs = { nixpkgs, ... }: let 7 | forAllSystems = function: 8 | nixpkgs.lib.genAttrs [ 9 | "x86_64-linux" 10 | "aarch64-linux" 11 | "x86_64-darwin" 12 | "aarch64-darwin" 13 | ] (system: 14 | function (import nixpkgs { 15 | inherit system; 16 | })); 17 | in { 18 | devShells = forAllSystems (pkgs: { 19 | default = pkgs.mkShell { 20 | buildInputs = [ 21 | pkgs.temurin-bin-21 22 | ]; 23 | }; 24 | }); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /extra-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 4 | 5 | plugins { 6 | id("net.megavex.scoreboardlibrary.base-conventions") 7 | alias(libs.plugins.kotlin) 8 | } 9 | 10 | kotlin { 11 | explicitApi() 12 | } 13 | 14 | dependencies { 15 | api(project(":scoreboard-library-api")) 16 | testImplementation(kotlin("test")) 17 | compileOnly(libs.spigotApi) 18 | } 19 | 20 | tasks.withType().configureEach { 21 | compilerOptions { 22 | jvmTarget = JvmTarget.JVM_1_8 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/team/enums/CollisionRule.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.team.enums; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | 5 | /** 6 | * Represents all possible collision rule values of a team. 7 | * 8 | * @since Minecraft 1.9 9 | */ 10 | public enum CollisionRule { 11 | ALWAYS("always"), 12 | NEVER("never"), 13 | PUSH_OTHER_TEAMS("pushOtherTeams"), 14 | PUSH_OWN_TEAM("pushOwnTeam"); 15 | 16 | private final String key; 17 | 18 | CollisionRule(String key) { 19 | this.key = key; 20 | } 21 | 22 | @ApiStatus.Internal 23 | public String key() { 24 | return this.key; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/team/enums/NameTagVisibility.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.team.enums; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | 5 | /** 6 | * Represents all possible name tag visibility rule values of a team. 7 | */ 8 | public enum NameTagVisibility { 9 | ALWAYS("always"), 10 | NEVER("never"), 11 | HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"), 12 | HIDE_FOR_OWN_TEAM("hideForOwnTeam"); 13 | 14 | private final String key; 15 | 16 | NameTagVisibility(String key) { 17 | this.key = key; 18 | } 19 | 20 | @ApiStatus.Internal 21 | public String key() { 22 | return this.key; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | ci: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: cachix/install-nix-action@v31 14 | - name: Cache Gradle 15 | uses: actions/cache@v4 16 | with: 17 | path: | 18 | ~/.gradle/caches 19 | ~/.gradle/wrapper 20 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 21 | restore-keys: | 22 | ${{ runner.os }}-gradle- 23 | - name: ./gradlew build 24 | run: nix develop -i -c sh -c "./gradlew build" 25 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1722987190, 6 | "narHash": "sha256-68hmex5efCiM2aZlAAEcQgmFI4ZwWt8a80vOeB/5w3A=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "21cc704b5e918c5fbf4f9fff22b4ac2681706d90", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-24.05", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/animation/SidebarAnimation.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component.animation; 2 | 3 | import net.megavex.scoreboardlibrary.api.sidebar.component.ComponentSidebarLayout; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * An animation that can be used with {@link ComponentSidebarLayout}s 8 | * 9 | * @param frame type 10 | */ 11 | public interface SidebarAnimation { 12 | /** 13 | * @return the current frame of the animation 14 | */ 15 | @NotNull T currentFrame(); 16 | 17 | /** 18 | * Advances to the next frame of the animation 19 | */ 20 | void nextFrame(); 21 | } 22 | -------------------------------------------------------------------------------- /versions/legacy/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/legacy/LegacyMinecraftClasses.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | // TODO: should be moved to MinecraftReflection at some point 7 | public final class LegacyMinecraftClasses { 8 | private static final String NMS_VERSION_STRING = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; 9 | 10 | private LegacyMinecraftClasses() { 11 | } 12 | 13 | public static @NotNull String server(String path) { 14 | return "net.minecraft.server." + NMS_VERSION_STRING + "." + path; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /commons/src/main/java/net/megavex/scoreboardlibrary/implementation/commons/LineRenderingStrategy.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.commons; 2 | 3 | /** 4 | * Represents different strategies of rendering sidebar lines. 5 | */ 6 | public enum LineRenderingStrategy { 7 | /** 8 | * For versions older than 1.13, where team properties are stored as strings and have a limit of 16 characters. 9 | */ 10 | LEGACY, 11 | /** 12 | * For versions 1.13-1.20.2, where team properties are stored as components with no limits. 13 | */ 14 | MODERN, 15 | /** 16 | * For versions 1.20.3+, where the score display name can be used instead of team prefix team. 17 | * Not currently being used. 18 | */ 19 | POST_MODERN 20 | } 21 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/scheduler/TaskScheduler.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.scheduler; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface TaskScheduler { 7 | static @NotNull TaskScheduler create(@NotNull Plugin plugin) { 8 | try { 9 | Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); 10 | return new FoliaTaskScheduler(plugin); 11 | } catch (ClassNotFoundException ignored) { 12 | return new BukkitTaskScheduler(plugin); 13 | } 14 | } 15 | 16 | @NotNull RunningTask runEveryTick(@NotNull Runnable runnable); 17 | 18 | void runNextTick(@NotNull Runnable runnable); 19 | } 20 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/team/TeamDisplayPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.team; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 4 | import org.bukkit.entity.Player; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Collection; 8 | 9 | public interface TeamDisplayPacketAdapter { 10 | default void updateTeamPackets() { 11 | } 12 | 13 | void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection players, @NotNull Collection entries); 14 | 15 | void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection players); 16 | } 17 | -------------------------------------------------------------------------------- /extra-kotlin/src/main/kotlin/net/megavex/scoreboardlibrary/extra/kotlin/SidebarExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.extra.kotlin 2 | 3 | import net.kyori.adventure.text.Component 4 | import net.megavex.scoreboardlibrary.api.sidebar.Sidebar 5 | import org.bukkit.entity.Player 6 | import java.util.* 7 | 8 | public inline val Sidebar.maxLines: Int get() = maxLines() 9 | public inline val Sidebar.locale: Locale? get() = locale() 10 | public operator fun Sidebar.get(line: Int): Component? = line(line) 11 | public operator fun Sidebar.set(line: Int, value: Component?): Unit = line(line, value) 12 | public inline var Sidebar.title: Component 13 | get() = title() 14 | set(value) { 15 | title(value) 16 | } 17 | public inline val Sidebar.players: Collection get() = players() 18 | -------------------------------------------------------------------------------- /extra-kotlin/src/main/kotlin/net/megavex/scoreboardlibrary/extra/kotlin/ScoreboardTeamExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.extra.kotlin 2 | 3 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam 4 | import net.megavex.scoreboardlibrary.api.team.TeamDisplay 5 | import net.megavex.scoreboardlibrary.api.team.TeamManager 6 | import org.bukkit.entity.Player 7 | 8 | public inline val ScoreboardTeam.teamManager: TeamManager get() = teamManager() 9 | public inline val ScoreboardTeam.name: String get() = name() 10 | public inline val ScoreboardTeam.defaultDisplay: TeamDisplay get() = defaultDisplay() 11 | public operator fun ScoreboardTeam.get(player: Player): TeamDisplay = display(player) 12 | public operator fun ScoreboardTeam.set(player: Player, teamDisplay: TeamDisplay): Unit = display(player, teamDisplay) 13 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/util/reflect/FieldAccessor.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.UnknownNullability; 5 | 6 | import java.lang.invoke.MethodHandle; 7 | 8 | public class FieldAccessor { 9 | private final MethodHandle setter; 10 | 11 | public FieldAccessor(@NotNull MethodHandle setter) { 12 | this.setter = setter; 13 | } 14 | 15 | public void set(@NotNull T instance, @UnknownNullability V value) { 16 | try { 17 | setter.invokeExact(instance, value); 18 | } catch (Throwable e) { 19 | throw new IllegalStateException("couldn't set value of field", e); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "scoreboard-library" 2 | 3 | include(":api") 4 | include(":commons") 5 | include(":implementation") 6 | include(":extra-kotlin") 7 | 8 | include(":packet-adapter-base") 9 | project(":packet-adapter-base").projectDir = file("versions/packet-adapter-base") 10 | 11 | include(":packetevents") 12 | project(":packetevents").projectDir = file("versions/packetevents") 13 | 14 | include(":modern") 15 | project(":modern").projectDir = file("versions/modern") 16 | 17 | include(":legacy") 18 | project(":legacy").projectDir = file("versions/legacy") 19 | 20 | // For backwards compatibility 21 | include(":v1_8_R3") 22 | project(":v1_8_R3").projectDir = file("versions/v1_8_R3") 23 | 24 | val modulePrefix = rootProject.name 25 | rootProject.children.forEach { 26 | it.name = "$modulePrefix-${it.name}" 27 | } 28 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/locale/LocaleLine.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo; 5 | import org.bukkit.entity.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Collection; 9 | 10 | public interface LocaleLine { 11 | @NotNull GlobalLineInfo info(); 12 | 13 | void value(@NotNull Component renderedComponent); 14 | 15 | void updateTeam(); 16 | 17 | default void resetOldPlayer() { 18 | } 19 | 20 | void sendScore(@NotNull Collection players); 21 | 22 | void show(@NotNull Collection players); 23 | 24 | void hide(@NotNull Collection players); 25 | } 26 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/team/TeamsPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.team; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 5 | import org.bukkit.entity.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public interface TeamsPacketAdapter { 9 | void removeTeam(@NotNull Iterable players); 10 | 11 | @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties); 12 | 13 | default @NotNull TeamDisplayPacketAdapter createLegacyTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties) { 14 | throw new UnsupportedOperationException(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/util/reflect/ConstructorAccessor.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.UnknownNullability; 5 | 6 | import java.lang.invoke.MethodHandle; 7 | 8 | public class ConstructorAccessor { 9 | private final MethodHandle handle; 10 | 11 | public ConstructorAccessor(@NotNull MethodHandle handle) { 12 | this.handle = handle; 13 | } 14 | 15 | public @NotNull T invoke(@UnknownNullability Object... args) { 16 | try { 17 | //noinspection unchecked 18 | return (T) handle.invokeExact(args); 19 | } catch (Throwable e) { 20 | throw new IllegalStateException("couldn't instantiate constructor", e); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/PacketAdapterProvider.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public interface PacketAdapterProvider { 10 | @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName); 11 | 12 | @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName); 13 | 14 | @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player); 15 | } 16 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/objective/DisplaySlotProvider.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective; 2 | 3 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectiveConstants; 5 | import net.minecraft.world.scores.DisplaySlot; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | // DisplaySlot was added in 1.20.2 so this has to be a separate class 9 | public final class DisplaySlotProvider { 10 | private static final DisplaySlot[] VALUES = DisplaySlot.values(); 11 | 12 | private DisplaySlotProvider() { 13 | } 14 | 15 | public static @NotNull DisplaySlot toNms(@NotNull ObjectiveDisplaySlot slot) { 16 | return VALUES[ObjectiveConstants.displaySlotIndex(slot)]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/scheduler/BukkitTaskScheduler.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.scheduler; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | class BukkitTaskScheduler implements TaskScheduler { 7 | private final Plugin plugin; 8 | 9 | public BukkitTaskScheduler(@NotNull Plugin plugin) { 10 | this.plugin = plugin; 11 | } 12 | 13 | @Override 14 | public @NotNull RunningTask runEveryTick(@NotNull Runnable runnable) { 15 | org.bukkit.scheduler.BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, 1, 1); 16 | return task::cancel; 17 | } 18 | 19 | @Override 20 | public void runNextTick(@NotNull Runnable runnable) { 21 | plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, 1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/src/test/java/net/megavex/scoreboardlibrary/api/animation/CollectionAnimationTest.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.animation; 2 | 3 | import net.megavex.scoreboardlibrary.api.sidebar.component.animation.CollectionSidebarAnimation; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class CollectionAnimationTest { 12 | @Test 13 | void loopTest() { 14 | List frames = Arrays.asList(0, 1, 2); 15 | CollectionSidebarAnimation animation = new CollectionSidebarAnimation<>(frames); 16 | assertEquals(0, animation.currentFrame()); 17 | animation.nextFrame(); 18 | assertEquals(1, animation.currentFrame()); 19 | animation.nextFrame(); 20 | assertEquals(2, animation.currentFrame()); 21 | animation.nextFrame(); 22 | assertEquals(0, animation.currentFrame()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/animation/FramedSidebarAnimation.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component.animation; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Unmodifiable; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * A {@link SidebarAnimation} with predetermined frames. 10 | * 11 | * @param frame type 12 | */ 13 | public interface FramedSidebarAnimation extends SidebarAnimation { 14 | /** 15 | * @return all frames in this animation 16 | */ 17 | @Unmodifiable @NotNull List frames(); 18 | 19 | /** 20 | * @return the index of the current frame 21 | */ 22 | int currentFrameIndex(); 23 | 24 | /** 25 | * Switches the current frame to specified index 26 | * 27 | * @param frameIndex the index of the new current frame 28 | * @throws ArrayIndexOutOfBoundsException if frame is out of bounds 29 | */ 30 | void switchFrame(int frameIndex); 31 | } 32 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/LineDrawable.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component; 2 | 3 | import net.kyori.adventure.text.ComponentLike; 4 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | /** 10 | * Something lines can be drawn to line by line. This is either the lines of a sidebar or its title. 11 | */ 12 | @ApiStatus.NonExtendable 13 | public interface LineDrawable { 14 | /** 15 | * Draws the next line, or does nothing if it has reached a limit. 16 | * 17 | * @param line line component 18 | */ 19 | default void drawLine(@NotNull ComponentLike line) { 20 | drawLine(line, null); 21 | } 22 | 23 | /** 24 | * Draws the next line with a custom score format, or does nothing if it has reached a limit. 25 | * 26 | * @param line line component 27 | * @param scoreFormat score format 28 | */ 29 | void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2023 Megavex and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/util/RegistryUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util; 2 | 3 | import net.minecraft.core.RegistryAccess; 4 | import org.bukkit.Bukkit; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | 9 | public final class RegistryUtil { 10 | public static final RegistryAccess MINECRAFT_REGISTRY; 11 | 12 | static { 13 | String cbPackage = Bukkit.getServer().getClass().getPackage().getName(); 14 | Class craftRegistry; 15 | try { 16 | craftRegistry = Class.forName(cbPackage + ".CraftRegistry"); 17 | } catch (ClassNotFoundException e) { 18 | throw new ExceptionInInitializerError(e); 19 | } 20 | 21 | try { 22 | Method method = craftRegistry.getDeclaredMethod("getMinecraftRegistry"); 23 | MINECRAFT_REGISTRY = (RegistryAccess) method.invoke(null); 24 | } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 25 | throw new ExceptionInInitializerError(e); 26 | } 27 | } 28 | 29 | private RegistryUtil() { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/team/TeamConstants.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.team; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public final class TeamConstants { 7 | public static final int LEGACY_CHAR_LIMIT = 16; 8 | 9 | public static final int MODE_CREATE = 0, 10 | MODE_REMOVE = 1, 11 | MODE_UPDATE = 2, 12 | MODE_ADD_ENTRIES = 3, 13 | MODE_REMOVE_ENTRIES = 4; 14 | 15 | private TeamConstants() { 16 | } 17 | 18 | public static int mode(@NotNull PropertiesPacketType packetType) { 19 | switch (packetType) { 20 | case CREATE: 21 | return MODE_CREATE; 22 | case UPDATE: 23 | return MODE_UPDATE; 24 | default: 25 | throw new IllegalStateException(); 26 | } 27 | } 28 | 29 | public static int mode(@NotNull EntriesPacketType packetType) { 30 | switch (packetType) { 31 | case ADD: 32 | return MODE_ADD_ENTRIES; 33 | case REMOVE: 34 | return MODE_REMOVE_ENTRIES; 35 | default: 36 | throw new IllegalStateException(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /versions/legacy/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/legacy/PacketAdapterProviderImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 7 | import org.bukkit.entity.Player; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @SuppressWarnings("unused") 11 | public class PacketAdapterProviderImpl implements PacketAdapterProvider { 12 | @Override 13 | public @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName) { 14 | return new TeamsPacketAdapterImpl(teamName); 15 | } 16 | 17 | @Override 18 | public @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName) { 19 | return new ObjectivePacketAdapterImpl(objectiveName); 20 | } 21 | 22 | @Override 23 | public @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player) { 24 | return LineRenderingStrategy.LEGACY; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | adventure = "4.25.0" 3 | devBundle = "1.21.10-R0.1-20251007.183616-3" # find latest here: https://repo.papermc.io/repository/maven-snapshots/io/papermc/paper/dev-bundle/1.21.9-R0.1-SNAPSHOT/maven-metadata.xml 4 | 5 | [libraries] 6 | spigotApi = "org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT" # do not update 7 | packetEvents = "com.github.retrooper:packetevents-spigot:2.11.0" 8 | buildIndra = { module = "net.kyori:indra-common", version = "4.0.0" } 9 | buildNmcp = "com.gradleup.nmcp:com.gradleup.nmcp.gradle.plugin:0.0.9" 10 | 11 | adventureApi = { module = "net.kyori:adventure-api", version.ref = "adventure" } 12 | adventureTextSerializerGson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } 13 | adventureTextSerializerLegacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } 14 | 15 | # TODO: v6 only supports java 17 16 | junitJupiter = "org.junit.jupiter:junit-jupiter:5.13.4" 17 | junitPlatformLauncher = "org.junit.platform:junit-platform-launcher:1.14.1" 18 | 19 | [plugins] 20 | kotlin = "org.jetbrains.kotlin.jvm:2.2.21" 21 | paperweight = "io.papermc.paperweight.userdev:2.0.0-beta.19" 22 | 23 | [bundles] 24 | adventure = [ "adventureApi", "adventureTextSerializerGson", "adventureTextSerializerLegacy" ] 25 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/ScoreFormatConverter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents; 2 | 3 | import net.kyori.adventure.translation.GlobalTranslator; 4 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.Locale; 9 | 10 | import static com.github.retrooper.packetevents.protocol.score.ScoreFormat.*; 11 | 12 | public final class ScoreFormatConverter { 13 | private ScoreFormatConverter() { 14 | } 15 | 16 | public static @Nullable com.github.retrooper.packetevents.protocol.score.ScoreFormat convert(@NotNull Locale locale, @Nullable ScoreFormat format) { 17 | if (format == null) { 18 | return null; 19 | } 20 | 21 | if (format == ScoreFormat.blank()) { 22 | return blankScore(); 23 | } else if (format instanceof ScoreFormat.Styled) { 24 | return styledScore(((ScoreFormat.Styled) format).style()); 25 | } else if (format instanceof ScoreFormat.Fixed) { 26 | return fixedScore(GlobalTranslator.render(((ScoreFormat.Fixed) format).content(), locale)); 27 | } else { 28 | throw new IllegalArgumentException("Invalid score format: " + format); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /versions/modern/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("net.megavex.scoreboardlibrary.base-conventions") 3 | alias(libs.plugins.paperweight) 4 | } 5 | 6 | dependencies { 7 | compileOnly(project(":scoreboard-library-packet-adapter-base")) { 8 | exclude(group = "org.spigotmc", module = "spigot-api") 9 | } 10 | paperweight.paperDevBundle(libs.versions.devBundle.get()) 11 | } 12 | 13 | tasks { 14 | compileJava { 15 | // Workaround to get it to compile targeting Java 8 while using NMS which targets 17 16 | // Why does this work? I have no idea 17 | options.release = null 18 | } 19 | 20 | // https://github.com/unnamed/hephaestus-engine/blob/db6deabe1ddb0a549306b0c4b519c3e79e6f1ea8/runtime-bukkit/adapt-v1_20_R3/build.gradle.kts#L25 21 | reobfJar { 22 | outputJar = file("build/libs/scoreboard-library-modern-$version-reobf.jar") 23 | } 24 | 25 | assemble { 26 | dependsOn(reobfJar) 27 | } 28 | 29 | javadoc { 30 | exclude("**") 31 | } 32 | } 33 | 34 | java { 35 | disableAutoTargetJvm() 36 | } 37 | 38 | indra { 39 | includeJavaSoftwareComponentInPublications(false) 40 | } 41 | 42 | publishing { 43 | publications.getByName("maven") { 44 | artifact(tasks.jar) { 45 | classifier = "mojmap" 46 | } 47 | 48 | artifact(tasks.reobfJar) 49 | artifact(tasks.javadocJar) 50 | artifact(tasks.sourcesJar) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/player/ScoreboardLibraryPlayer.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.player; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerImpl; 4 | import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar; 5 | import net.megavex.scoreboardlibrary.implementation.team.TeamManagerImpl; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.UUID; 10 | 11 | public class ScoreboardLibraryPlayer { 12 | private final DisplayableQueue teamManagerQueue; 13 | private final DisplayableQueue objectiveManagerQueue; 14 | private final DisplayableQueue sidebarQueue; 15 | 16 | public ScoreboardLibraryPlayer(@NotNull Player player) { 17 | UUID uuid = player.getUniqueId(); 18 | this.teamManagerQueue = new DisplayableQueue<>(uuid); 19 | this.objectiveManagerQueue = new DisplayableQueue<>(uuid); 20 | this.sidebarQueue = new DisplayableQueue<>(uuid); 21 | } 22 | 23 | public @NotNull DisplayableQueue teamManagerQueue() { 24 | return teamManagerQueue; 25 | } 26 | 27 | public @NotNull DisplayableQueue objectiveManagerQueue() { 28 | return objectiveManagerQueue; 29 | } 30 | 31 | public @NotNull DisplayableQueue sidebarQueue() { 32 | return sidebarQueue; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/team/LegacyTeamDisplayPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team; 2 | 3 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.Collection; 12 | 13 | public class LegacyTeamDisplayPacketAdapter extends AbstractTeamDisplayPacketAdapter { 14 | public LegacyTeamDisplayPacketAdapter(@NotNull PacketSender> sender, @NotNull String teamName, @NotNull ImmutableTeamProperties properties) { 15 | super(sender, teamName, properties); 16 | } 17 | 18 | @Override 19 | public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection players) { 20 | LocalePacketUtil.sendLocalePackets( 21 | sender, 22 | players, 23 | locale -> new WrapperPlayServerTeamsLegacy( 24 | teamName, 25 | properties, 26 | mode(packetType) 27 | ) 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/objective/ObjectivePacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.objective; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot; 5 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.Collection; 13 | 14 | public interface ObjectivePacketAdapter { 15 | @NotNull String objectiveName(); 16 | 17 | void display(@NotNull Collection players, @NotNull ObjectiveDisplaySlot slot); 18 | 19 | void sendProperties( 20 | @NotNull Collection players, 21 | @NotNull PropertiesPacketType packetType, 22 | @NotNull Component value, 23 | @NotNull ObjectiveRenderType renderType, 24 | @Nullable ScoreFormat scoreFormat 25 | ); 26 | 27 | void remove(@NotNull Collection players); 28 | 29 | void sendScore( 30 | @NotNull Collection players, 31 | @NotNull String entry, 32 | int value, 33 | @Nullable Component display, 34 | @Nullable ScoreFormat scoreFormat 35 | ); 36 | 37 | void removeScore(@NotNull Collection players, @NotNull String entry); 38 | } 39 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/util/LocalePacketUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.util; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider; 4 | import net.megavex.scoreboardlibrary.implementation.commons.LocaleProvider; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Collection; 10 | import java.util.Locale; 11 | import java.util.Map; 12 | import java.util.function.Function; 13 | 14 | public final class LocalePacketUtil { 15 | private LocalePacketUtil() { 16 | } 17 | 18 | public static

void sendLocalePackets( 19 | @NotNull PacketSender

sender, 20 | @NotNull Collection players, 21 | @NotNull Function packetFunction 22 | ) { 23 | if (players.isEmpty()) { 24 | return; 25 | } 26 | 27 | if (players.size() == 1) { 28 | Player player = players.iterator().next(); 29 | P packet = packetFunction.apply(LocaleProvider.locale(player)); 30 | sender.sendPacket(player, packet); 31 | return; 32 | } 33 | 34 | Map map = CollectionProvider.map(1); 35 | for (Player player : players) { 36 | Locale locale = LocaleProvider.locale(player); 37 | P packet = map.computeIfAbsent(locale, i -> packetFunction.apply(locale)); 38 | sender.sendPacket(player, packet); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/ImmutableTeamProperties.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule; 6 | import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.Collection; 11 | 12 | public interface ImmutableTeamProperties { 13 | default @NotNull Collection syncedEntries() { 14 | return ImmutableList.of(); 15 | } 16 | 17 | @NotNull T displayName(); 18 | 19 | @NotNull T prefix(); 20 | 21 | @NotNull T suffix(); 22 | 23 | default boolean friendlyFire() { 24 | return false; 25 | } 26 | 27 | default boolean canSeeFriendlyInvisibles() { 28 | return false; 29 | } 30 | 31 | default @NotNull NameTagVisibility nameTagVisibility() { 32 | return NameTagVisibility.ALWAYS; 33 | } 34 | 35 | default @NotNull CollisionRule collisionRule() { 36 | return CollisionRule.ALWAYS; 37 | } 38 | 39 | default @Nullable NamedTextColor playerColor() { 40 | return null; 41 | } 42 | 43 | default int packOptions() { 44 | int options = 0; 45 | 46 | if (this.friendlyFire()) { 47 | options |= 1; 48 | } 49 | 50 | if (this.canSeeFriendlyInvisibles()) { 51 | options |= 2; 52 | } 53 | 54 | return options; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /versions/legacy/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/legacy/ChatColorUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy; 2 | 3 | import net.kyori.adventure.text.format.NamedTextColor; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.reflect.Method; 10 | 11 | public final class ChatColorUtil { 12 | private static final MethodHandle FROM_NAME_METHOD, GET_INDEX_METHOD; 13 | 14 | static { 15 | Class enumChatFormatClass = ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("EnumChatFormat")); 16 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 17 | 18 | try { 19 | Method fromNameMethod = enumChatFormatClass.getMethod("b", String.class); 20 | FROM_NAME_METHOD = lookup.unreflect(fromNameMethod); 21 | 22 | Method getIndexMethod = enumChatFormatClass.getMethod("b"); 23 | GET_INDEX_METHOD = lookup.unreflect(getIndexMethod); 24 | } catch (NoSuchMethodException | IllegalAccessException e) { 25 | throw new ExceptionInInitializerError(e); 26 | } 27 | } 28 | 29 | private ChatColorUtil() { 30 | } 31 | 32 | public static int getColorIndex(@NotNull NamedTextColor color) { 33 | String name = NamedTextColor.NAMES.key(color); 34 | try { 35 | Object format = FROM_NAME_METHOD.invoke(name); 36 | return (int) GET_INDEX_METHOD.invoke(format); 37 | } catch (Throwable e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/SidebarUpdaterTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Iterator; 8 | import java.util.logging.Level; 9 | 10 | public class SidebarUpdaterTask implements Runnable { 11 | private final ScoreboardLibraryImpl scoreboardLibrary; 12 | private final RunningTask task; 13 | private final Object lock = new Object(); 14 | 15 | public SidebarUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) { 16 | this.scoreboardLibrary = scoreboardLibrary; 17 | this.task = scoreboardLibrary.taskScheduler().runEveryTick(this); 18 | } 19 | 20 | public @NotNull RunningTask task() { 21 | return task; 22 | } 23 | 24 | public @NotNull Object lock() { 25 | return lock; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | synchronized (lock) { 31 | Iterator iterator = scoreboardLibrary.sidebars().iterator(); 32 | while (iterator.hasNext()) { 33 | AbstractSidebar sidebar = iterator.next(); 34 | boolean result; 35 | try { 36 | result = sidebar.tick(); 37 | } catch (Exception e) { 38 | scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating a Sidebar instance", e); 39 | continue; 40 | } 41 | 42 | if (!result) { 43 | iterator.remove(); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/team/TeamUpdaterTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.team; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Iterator; 8 | import java.util.logging.Level; 9 | 10 | public class TeamUpdaterTask implements Runnable { 11 | private final ScoreboardLibraryImpl scoreboardLibrary; 12 | private final RunningTask task; 13 | private final Object lock = new Object(); 14 | 15 | public TeamUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) { 16 | this.scoreboardLibrary = scoreboardLibrary; 17 | this.task = scoreboardLibrary.taskScheduler().runEveryTick(this); 18 | } 19 | 20 | public @NotNull RunningTask task() { 21 | return task; 22 | } 23 | 24 | public @NotNull Object lock() { 25 | return lock; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | synchronized (lock) { 31 | Iterator iterator = scoreboardLibrary.teamManagers().iterator(); 32 | while (iterator.hasNext()) { 33 | TeamManagerImpl teamManager = iterator.next(); 34 | boolean result; 35 | try { 36 | result = teamManager.tick(); 37 | } catch (Exception e) { 38 | scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating a TeamManager instance", e); 39 | continue; 40 | } 41 | 42 | if (!result) { 43 | iterator.remove(); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/animation/CollectionSidebarAnimation.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component.animation; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Unmodifiable; 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | /** 12 | * Implementation of {@link FramedSidebarAnimation} using a predefined collection of frames. 13 | * 14 | * @param frame type 15 | */ 16 | public final class CollectionSidebarAnimation implements FramedSidebarAnimation { 17 | private final List frames; 18 | private int currentFrameIndex; 19 | 20 | public CollectionSidebarAnimation(@NotNull Collection frames) { 21 | Preconditions.checkNotNull(frames); 22 | Preconditions.checkArgument(!frames.isEmpty()); 23 | this.frames = ImmutableList.copyOf(frames); 24 | } 25 | 26 | @Override 27 | public @Unmodifiable @NotNull List frames() { 28 | return frames; 29 | } 30 | 31 | @Override 32 | public int currentFrameIndex() { 33 | return currentFrameIndex; 34 | } 35 | 36 | @Override 37 | public void switchFrame(int frameIndex) { 38 | if (frameIndex < 0 || frameIndex >= frames.size()) { 39 | throw new ArrayIndexOutOfBoundsException(); 40 | } 41 | 42 | currentFrameIndex = frameIndex; 43 | } 44 | 45 | @Override 46 | public @NotNull T currentFrame() { 47 | return frames.get(currentFrameIndex); 48 | } 49 | 50 | @Override 51 | public void nextFrame() { 52 | if (++currentFrameIndex == frames.size()) { 53 | currentFrameIndex = 0; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopObjectiveManager.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager; 5 | import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.*; 10 | 11 | class NoopObjectiveManager implements ObjectiveManager { 12 | private final Map objectives = new HashMap<>(); 13 | private final Set players = new HashSet<>(); 14 | private boolean isClosed = true; 15 | 16 | @Override 17 | public @NotNull ScoreboardObjective create(@NotNull String name) { 18 | return objectives.computeIfAbsent(name, i -> new NoopScoreboardObjective()); 19 | } 20 | 21 | @Override 22 | public void remove(@NotNull ScoreboardObjective objective) { 23 | objectives.values().remove(objective); 24 | } 25 | 26 | @Override 27 | public void display(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjective objective) { 28 | } 29 | 30 | @Override 31 | public @NotNull Collection players() { 32 | return Collections.unmodifiableSet(players); 33 | } 34 | 35 | @Override 36 | public boolean addPlayer(@NotNull Player player) { 37 | return players.add(player); 38 | } 39 | 40 | @Override 41 | public boolean removePlayer(@NotNull Player player) { 42 | return players.remove(player); 43 | } 44 | 45 | @Override 46 | public void close() { 47 | isClosed = true; 48 | } 49 | 50 | @Override 51 | public boolean closed() { 52 | return isClosed; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/objective/ObjectiveUpdaterTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.objective; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Iterator; 8 | import java.util.logging.Level; 9 | 10 | public class ObjectiveUpdaterTask implements Runnable { 11 | private final ScoreboardLibraryImpl scoreboardLibrary; 12 | private final RunningTask task; 13 | private final Object lock = new Object(); 14 | 15 | public ObjectiveUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) { 16 | this.scoreboardLibrary = scoreboardLibrary; 17 | this.task = scoreboardLibrary.taskScheduler().runEveryTick(this); 18 | } 19 | 20 | public @NotNull RunningTask task() { 21 | return task; 22 | } 23 | 24 | public @NotNull Object lock() { 25 | return lock; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | synchronized (lock) { 31 | Iterator iterator = scoreboardLibrary.objectiveManagers().iterator(); 32 | while (iterator.hasNext()) { 33 | ObjectiveManagerImpl objectiveManager = iterator.next(); 34 | boolean result; 35 | try { 36 | result = objectiveManager.tick(); 37 | } catch (Exception e) { 38 | scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating an ObjectiveManager instance", e); 39 | continue; 40 | } 41 | 42 | if (!result) { 43 | iterator.remove(); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopScoreboardLibrary.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import net.megavex.scoreboardlibrary.api.ScoreboardLibrary; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager; 5 | import net.megavex.scoreboardlibrary.api.sidebar.Sidebar; 6 | import net.megavex.scoreboardlibrary.api.team.TeamManager; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.jetbrains.annotations.Range; 10 | 11 | import java.util.Locale; 12 | 13 | /** 14 | * No-op implementation of {@link ScoreboardLibrary}. 15 | * Can be used as a fallback when there is no packet adapter available 16 | * and for unit testing. 17 | */ 18 | public final class NoopScoreboardLibrary implements ScoreboardLibrary { 19 | private boolean closed = false; 20 | 21 | @Override 22 | public @NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines, @Nullable Locale locale, @NotNull String objectiveName) { 23 | checkClosed(); 24 | return new NoopSidebar(maxLines, objectiveName, locale); 25 | } 26 | 27 | @Override 28 | public @NotNull TeamManager createTeamManager() { 29 | checkClosed(); 30 | return new NoopTeamManager(); 31 | } 32 | 33 | @Override 34 | public @NotNull ObjectiveManager createObjectiveManager() { 35 | checkClosed(); 36 | return new NoopObjectiveManager(); 37 | } 38 | 39 | @Override 40 | public void close() { 41 | closed = true; 42 | } 43 | 44 | @Override 45 | public boolean closed() { 46 | return closed; 47 | } 48 | 49 | private void checkClosed() { 50 | if (closed) { 51 | throw new IllegalStateException("NoopScoreboardLibrary is closed"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /extra-kotlin/src/main/kotlin/net/megavex/scoreboardlibrary/extra/kotlin/TeamDisplayExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.extra.kotlin 2 | 3 | import net.kyori.adventure.text.Component 4 | import net.kyori.adventure.text.format.NamedTextColor 5 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam 6 | import net.megavex.scoreboardlibrary.api.team.TeamDisplay 7 | import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule 8 | import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility 9 | 10 | public inline val TeamDisplay.team: ScoreboardTeam get() = team() 11 | public inline val TeamDisplay.entries: Collection get() = entries() 12 | 13 | public inline var TeamDisplay.displayName: Component 14 | get() = displayName() 15 | set(value) { 16 | displayName(value) 17 | } 18 | 19 | public inline var TeamDisplay.prefix: Component 20 | get() = prefix() 21 | set(value) { 22 | prefix(value) 23 | } 24 | 25 | public inline var TeamDisplay.suffix: Component 26 | get() = suffix() 27 | set(value) { 28 | suffix(value) 29 | } 30 | 31 | public inline var TeamDisplay.isFriendlyFire: Boolean 32 | get() = friendlyFire() 33 | set(value) { 34 | friendlyFire(value) 35 | } 36 | 37 | public inline var TeamDisplay.canSeeFriendlyInvisibles: Boolean 38 | get() = canSeeFriendlyInvisibles() 39 | set(value) { 40 | canSeeFriendlyInvisibles(value) 41 | } 42 | 43 | public inline var TeamDisplay.nameTagVisibility: NameTagVisibility 44 | get() = nameTagVisibility() 45 | set(value) { 46 | nameTagVisibility(value) 47 | } 48 | 49 | public inline var TeamDisplay.collisionRule: CollisionRule 50 | get() = collisionRule() 51 | set(value) { 52 | collisionRule(value) 53 | } 54 | 55 | public inline var TeamDisplay.playerColor: NamedTextColor? 56 | get() = playerColor() 57 | set(value) { 58 | playerColor(value) 59 | } 60 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/locale/PostModernLocaleLine.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo; 5 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Collection; 10 | 11 | // Implementation for versions 1.20.3+ 12 | // This is not yet being used as it might break on servers with plugins such as ViaBackwards, 13 | // however it will once Mojang decides to remove legacy chat format support 14 | public class PostModernLocaleLine implements LocaleLine { 15 | private final GlobalLineInfo info; 16 | private final SidebarLineHandler handler; 17 | 18 | public PostModernLocaleLine(GlobalLineInfo info, SidebarLineHandler handler) { 19 | this.info = info; 20 | this.handler = handler; 21 | } 22 | 23 | @Override 24 | public @NotNull GlobalLineInfo info() { 25 | return info; 26 | } 27 | 28 | @Override 29 | public void value(@NotNull Component renderedComponent) { 30 | } 31 | 32 | @Override 33 | public void updateTeam() { 34 | } 35 | 36 | @Override 37 | public void sendScore(@NotNull Collection players) { 38 | handler.localeLineHandler() 39 | .sidebar() 40 | .packetAdapter() 41 | .sendScore(players, info.player(), info.objectiveScore(), info.value(), info.scoreFormat()); 42 | } 43 | 44 | @Override 45 | public void show(@NotNull Collection players) { 46 | sendScore(players); 47 | } 48 | 49 | @Override 50 | public void hide(@NotNull Collection players) { 51 | handler.localeLineHandler().sidebar().packetAdapter().removeScore(players, info.player()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/player/DisplayableQueue.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.player; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.entity.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | public class DisplayableQueue { 13 | private final UUID playerUuid; 14 | private final List queue = CollectionProvider.list(1); 15 | 16 | public DisplayableQueue(@NotNull UUID playerUuid) { 17 | this.playerUuid = playerUuid; 18 | } 19 | 20 | public synchronized @Nullable T current() { 21 | if (queue.isEmpty()) { 22 | return null; 23 | } else { 24 | return queue.get(0); 25 | } 26 | } 27 | 28 | public synchronized void add(@NotNull T displayable) { 29 | if (queue.contains(displayable)) { 30 | throw new IllegalStateException("displayable already registered"); 31 | } 32 | 33 | queue.add(displayable); 34 | 35 | if (current() == displayable) { 36 | Player player = Bukkit.getPlayer(playerUuid); 37 | if (player != null) { 38 | displayable.display(player); 39 | } 40 | } 41 | } 42 | 43 | public synchronized void remove(@NotNull T displayable) { 44 | boolean wasCurrent = displayable == current(); 45 | if (!queue.remove(displayable)) { 46 | throw new IllegalStateException("displayable not registered"); 47 | } 48 | 49 | if (!wasCurrent) { 50 | return; 51 | } 52 | 53 | T newDisplayable = current(); 54 | if (newDisplayable != null) { 55 | Player player = Bukkit.getPlayer(playerUuid); 56 | if (player != null) { 57 | newDisplayable.display(player); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/SingleLocaleSidebar.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider; 5 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.Locale; 11 | import java.util.Set; 12 | import java.util.function.Consumer; 13 | 14 | public class SingleLocaleSidebar extends AbstractSidebar { 15 | private final Locale locale; 16 | private final LocaleLineHandler sidebar; 17 | private final Set internalPlayers; 18 | 19 | public SingleLocaleSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int size, @NotNull String objectiveName, @NotNull Locale locale) { 20 | super(scoreboardLibrary, size, objectiveName); 21 | this.locale = locale; 22 | this.sidebar = new LocaleLineHandler(this, locale); 23 | this.internalPlayers = CollectionProvider.set(8); 24 | } 25 | 26 | @Override 27 | public @Nullable Locale locale() { 28 | return locale; 29 | } 30 | 31 | @Override 32 | protected @NotNull Set internalPlayers() { 33 | return internalPlayers; 34 | } 35 | 36 | @Override 37 | protected @Nullable LocaleLineHandler addPlayer0(@NotNull Player player) { 38 | if (!internalPlayers.add(player)) return null; 39 | return sidebar; 40 | } 41 | 42 | @Override 43 | protected @Nullable LocaleLineHandler removePlayer0(@NotNull Player player) { 44 | if (!internalPlayers.remove(player)) return null; 45 | return sidebar; 46 | } 47 | 48 | @Override 49 | protected void forEachLineHandler(@NotNull Consumer consumer) { 50 | consumer.accept(sidebar); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /versions/packet-adapter-base/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/objective/ObjectiveConstants.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.objective; 2 | 3 | import net.kyori.adventure.text.format.NamedTextColor; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot; 5 | import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 7 | import org.bukkit.ChatColor; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class ObjectiveConstants { 11 | public static final int MODE_CREATE = 0, 12 | MODE_REMOVE = 1, 13 | MODE_UPDATE = 2, 14 | LEGACY_VALUE_CHAR_LIMIT = 32; 15 | 16 | private ObjectiveConstants() { 17 | } 18 | 19 | public static int mode(@NotNull PropertiesPacketType packetType) { 20 | switch (packetType) { 21 | case CREATE: 22 | return MODE_CREATE; 23 | case UPDATE: 24 | return MODE_UPDATE; 25 | default: 26 | throw new IllegalStateException(); 27 | } 28 | } 29 | 30 | public static int displaySlotIndex(@NotNull ObjectiveDisplaySlot slot) { 31 | return displaySlotIndex(slot, true); 32 | } 33 | 34 | public static int displaySlotIndex(@NotNull ObjectiveDisplaySlot slot, boolean isAbove1_8) { 35 | if (slot instanceof ObjectiveDisplaySlot.PlayerList) { 36 | return 0; 37 | } 38 | 39 | if (slot instanceof ObjectiveDisplaySlot.Sidebar || (!isAbove1_8 && slot instanceof ObjectiveDisplaySlot.TeamSidebar)) { 40 | return 1; 41 | } 42 | 43 | if (slot instanceof ObjectiveDisplaySlot.BelowName) { 44 | return 2; 45 | } 46 | 47 | NamedTextColor color = ((ObjectiveDisplaySlot.TeamSidebar) slot).teamColor(); 48 | char legacyChar = LegacyFormatUtil.getChar(color); 49 | ChatColor chatColor = ChatColor.getByChar(legacyChar); 50 | return 3 + chatColor.ordinal(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/team/ScoreboardTeam.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.team; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Represents a scoreboard team. 8 | * To get an instance of this interface, use {@link TeamManager#createIfAbsent}. 9 | * Note: this interface is not thread-safe, meaning you can only use it from one thread at a time, 10 | * although it does not have to be the main thread. 11 | * 12 | * @see Minecraft Wiki 13 | */ 14 | public interface ScoreboardTeam { 15 | /** 16 | * @return TeamManager of this team 17 | */ 18 | @NotNull TeamManager teamManager(); 19 | 20 | /** 21 | * @return name of this team 22 | */ 23 | @NotNull String name(); 24 | 25 | /** 26 | * Gets the default {@link TeamDisplay} of this team. 27 | * All players are added to this display by default. 28 | * 29 | * @return default team display 30 | */ 31 | @NotNull TeamDisplay defaultDisplay(); 32 | 33 | /** 34 | * Gets the {@link TeamDisplay} that a player sees. 35 | * 36 | * @param player player 37 | * @return team display the player sees 38 | * @throws IllegalArgumentException if player is not in the TeamManager 39 | */ 40 | @NotNull TeamDisplay display(@NotNull Player player); 41 | 42 | /** 43 | * Updates the team {@link TeamDisplay} that a player sees for this team. 44 | * 45 | * @param player player 46 | * @param teamDisplay new {@link TeamDisplay} of player 47 | * @throws IllegalArgumentException if player is not in the TeamManager 48 | */ 49 | void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay); 50 | 51 | /** 52 | * Creates a new {@link TeamDisplay}. 53 | * To show it to players, use {@link #display(Player, TeamDisplay)}. 54 | * 55 | * @return newly created team display 56 | */ 57 | @NotNull TeamDisplay createDisplay(); 58 | } 59 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/team/TeamsPacketAdapterImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team; 2 | 3 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 4 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; 5 | import net.kyori.adventure.text.Component; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 10 | import org.bukkit.entity.Player; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class TeamsPacketAdapterImpl implements TeamsPacketAdapter { 14 | private final PacketSender> sender; 15 | private final String teamName; 16 | private WrapperPlayServerTeams removePacket; 17 | 18 | public TeamsPacketAdapterImpl(PacketSender> sender, String teamName) { 19 | this.sender = sender; 20 | this.teamName = teamName; 21 | } 22 | 23 | @Override 24 | public void removeTeam(@NotNull Iterable players) { 25 | if (removePacket == null) { 26 | removePacket = new WrapperPlayServerTeams( 27 | teamName, 28 | WrapperPlayServerTeams.TeamMode.REMOVE, 29 | (WrapperPlayServerTeams.ScoreBoardTeamInfo) null 30 | ); 31 | } 32 | 33 | sender.sendPacket(players, removePacket); 34 | } 35 | 36 | @Override 37 | public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties) { 38 | return new AdventureTeamDisplayPacketAdapter(sender, teamName, properties); 39 | } 40 | 41 | @Override 42 | public @NotNull TeamDisplayPacketAdapter createLegacyTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties) { 43 | return new LegacyTeamDisplayPacketAdapter(sender, teamName, properties); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/GlobalLineInfo.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 6 | import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class GlobalLineInfo { 11 | private final String player; 12 | private final int line; 13 | private final TeamsPacketAdapter packetAdapter; 14 | private Component value; 15 | private ScoreFormat scoreFormat; 16 | private int objectiveScore; 17 | private boolean updateScore; 18 | 19 | public GlobalLineInfo(@NotNull AbstractSidebar sidebar, @NotNull String player, int line) { 20 | this.player = player; 21 | this.line = line; 22 | this.packetAdapter = sidebar 23 | .scoreboardLibrary() 24 | .packetAdapter() 25 | .createTeamPacketAdapter("sidebar_line_" + line); 26 | } 27 | 28 | public @NotNull String player() { 29 | return player; 30 | } 31 | 32 | public int line() { 33 | return line; 34 | } 35 | 36 | public @NotNull TeamsPacketAdapter packetAdapter() { 37 | return packetAdapter; 38 | } 39 | 40 | public @Nullable Component value() { 41 | return value; 42 | } 43 | 44 | public void value(@Nullable Component value) { 45 | this.value = value; 46 | } 47 | 48 | public @Nullable ScoreFormat scoreFormat() { 49 | return scoreFormat; 50 | } 51 | 52 | public void scoreFormat(@Nullable ScoreFormat scoreFormat) { 53 | this.scoreFormat = scoreFormat; 54 | } 55 | 56 | public int objectiveScore() { 57 | return objectiveScore; 58 | } 59 | 60 | public void objectiveScore(int objectiveScore) { 61 | this.objectiveScore = objectiveScore; 62 | } 63 | 64 | public boolean updateScore() { 65 | return updateScore; 66 | } 67 | 68 | public void updateScore(boolean updateScore) { 69 | this.updateScore = updateScore; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/objective/ObjectiveScore.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.objective; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.ComponentLike; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * Represents an objective score. 11 | */ 12 | public final class ObjectiveScore { 13 | private final int value; 14 | private final Component displayName; 15 | private final ScoreFormat format; 16 | 17 | public ObjectiveScore(int value, @Nullable ComponentLike displayName, @Nullable ScoreFormat format) { 18 | this.value = value; 19 | this.displayName = displayName == null ? null : displayName.asComponent(); 20 | this.format = format; 21 | } 22 | 23 | /** 24 | * @return score value 25 | */ 26 | public int value() { 27 | return value; 28 | } 29 | 30 | /** 31 | * Gets the score display. 32 | * 33 | * @return score display 34 | * @since Minecraft 1.20.3 35 | */ 36 | public @Nullable Component displayName() { 37 | return displayName; 38 | } 39 | 40 | /** 41 | * Gets the score format, which determines how the scores are rendered in clients. 42 | * 43 | * @return score format 44 | * @since Minecraft 1.20.3 45 | */ 46 | public @Nullable ScoreFormat format() { 47 | return format; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "ObjectiveScore{" + 53 | "value=" + value + 54 | ", displayName=" + displayName + 55 | ", format=" + format + 56 | '}'; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object o) { 61 | if (this == o) return true; 62 | if (o == null || getClass() != o.getClass()) return false; 63 | 64 | ObjectiveScore that = (ObjectiveScore) o; 65 | 66 | if (value != that.value) return false; 67 | if (!Objects.equals(displayName, that.displayName)) return false; 68 | return Objects.equals(format, that.format); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | int result = value; 74 | result = 31 * result + (displayName != null ? displayName.hashCode() : 0); 75 | result = 31 * result + (format != null ? format.hashCode() : 0); 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /versions/legacy/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/legacy/LegacyPacketSender.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.MinecraftClasses; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.lang.invoke.MethodHandle; 9 | import java.lang.invoke.MethodHandles; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | 13 | public final class LegacyPacketSender implements PacketSender { 14 | public static final LegacyPacketSender INSTANCE = new LegacyPacketSender(); 15 | 16 | private static final MethodHandle GET_HANDLE_METHOD, PLAYER_CONNECTION_FIELD, SEND_PACKET_METHOD; 17 | 18 | static { 19 | Class packetClass = ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("Packet")); 20 | Class craftPlayerClass = ReflectUtil.getClassOrThrow(MinecraftClasses.craftBukkit("entity.CraftPlayer")); 21 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 22 | 23 | try { 24 | Method getHandleMethod = craftPlayerClass.getMethod("getHandle"); 25 | GET_HANDLE_METHOD = lookup.unreflect(getHandleMethod); 26 | 27 | Field playerConnectionField = getHandleMethod.getReturnType().getField("playerConnection"); 28 | PLAYER_CONNECTION_FIELD = lookup.unreflectGetter(playerConnectionField); 29 | 30 | Method sendPacketMethod = playerConnectionField.getType().getMethod("sendPacket", packetClass); 31 | SEND_PACKET_METHOD = lookup.unreflect(sendPacketMethod); 32 | } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException e) { 33 | throw new ExceptionInInitializerError(e); 34 | } 35 | } 36 | 37 | private LegacyPacketSender() { 38 | } 39 | 40 | @Override 41 | public void sendPacket(Player player, Object packet) { 42 | try { 43 | Object handle = GET_HANDLE_METHOD.invoke(player); 44 | Object playerConnection = PLAYER_CONNECTION_FIELD.invoke(handle); 45 | SEND_PACKET_METHOD.invoke(playerConnection, packet); 46 | } catch (Throwable e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/SidebarTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class SidebarTask { 7 | private SidebarTask() { 8 | } 9 | 10 | public static final class Close extends SidebarTask { 11 | public static final Close INSTANCE = new Close(); 12 | 13 | private Close() { 14 | } 15 | } 16 | 17 | public static final class AddPlayer extends SidebarTask { 18 | private final Player player; 19 | 20 | public AddPlayer(@NotNull Player player) { 21 | this.player = player; 22 | } 23 | 24 | public @NotNull Player player() { 25 | return player; 26 | } 27 | } 28 | 29 | public static final class RemovePlayer extends SidebarTask { 30 | private final Player player; 31 | 32 | public RemovePlayer(@NotNull Player players) { 33 | this.player = players; 34 | } 35 | 36 | public @NotNull Player player() { 37 | return player; 38 | } 39 | } 40 | 41 | public static final class ReloadPlayer extends SidebarTask { 42 | private final Player player; 43 | 44 | public ReloadPlayer(@NotNull Player players) { 45 | this.player = players; 46 | } 47 | 48 | public @NotNull Player player() { 49 | return player; 50 | } 51 | } 52 | 53 | public static final class UpdateLine extends SidebarTask { 54 | private final int line; 55 | private final boolean updateValue, updateScore; 56 | 57 | public UpdateLine(int line, boolean updateValue, boolean updateScore) { 58 | this.line = line; 59 | this.updateValue = updateValue; 60 | this.updateScore = updateScore; 61 | } 62 | 63 | public int line() { 64 | return line; 65 | } 66 | 67 | public boolean updateValue() { 68 | return updateValue; 69 | } 70 | 71 | public boolean updateScore() { 72 | return updateScore; 73 | } 74 | } 75 | 76 | public static final class UpdateScores extends SidebarTask { 77 | public static final UpdateScores INSTANCE = new UpdateScores(); 78 | 79 | private UpdateScores() { 80 | } 81 | } 82 | 83 | public static final class UpdateTitle extends SidebarTask { 84 | public static final UpdateTitle INSTANCE = new UpdateTitle(); 85 | 86 | private UpdateTitle() { 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopScoreboardTeam.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam; 5 | import net.megavex.scoreboardlibrary.api.team.TeamDisplay; 6 | import net.megavex.scoreboardlibrary.api.team.TeamManager; 7 | import org.bukkit.entity.Player; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | 14 | class NoopScoreboardTeam implements ScoreboardTeam { 15 | private final NoopTeamManager teamManager; 16 | private final String name; 17 | private final Map displayMap = new HashMap<>(); 18 | private final NoopTeamDisplay defaultDisplay = new NoopTeamDisplay(this); 19 | 20 | NoopScoreboardTeam(@NotNull NoopTeamManager teamManager, String name) { 21 | this.teamManager = teamManager; 22 | this.name = name; 23 | } 24 | 25 | @Override 26 | public @NotNull TeamManager teamManager() { 27 | return teamManager; 28 | } 29 | 30 | @Override 31 | public @NotNull String name() { 32 | return name; 33 | } 34 | 35 | @Override 36 | public @NotNull NoopTeamDisplay defaultDisplay() { 37 | return defaultDisplay; 38 | } 39 | 40 | @Override 41 | public @NotNull TeamDisplay display(@NotNull Player player) { 42 | Preconditions.checkNotNull(player); 43 | 44 | if (!teamManager.players().contains(player)) { 45 | throw new IllegalArgumentException("player not in TeamManager"); 46 | } 47 | 48 | return Objects.requireNonNull(displayMap.get(player)); 49 | } 50 | 51 | @Override 52 | public void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay) { 53 | Preconditions.checkNotNull(player); 54 | Preconditions.checkNotNull(teamDisplay); 55 | 56 | if (!teamManager.players().contains(player)) { 57 | throw new IllegalArgumentException("player not in TeamManager"); 58 | } 59 | 60 | if (teamDisplay.team() != this || !(teamDisplay instanceof NoopTeamDisplay)) { 61 | throw new IllegalArgumentException("invalid TeamDisplay"); 62 | } 63 | 64 | displayMap.put(player, teamDisplay); 65 | } 66 | 67 | @Override 68 | public @NotNull TeamDisplay createDisplay() { 69 | return new NoopTeamDisplay(this); 70 | } 71 | 72 | @NotNull Map displayMap() { 73 | return displayMap; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /commons/src/main/java/net/megavex/scoreboardlibrary/implementation/commons/LegacyFormatUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.commons; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 6 | import net.kyori.adventure.text.serializer.legacy.LegacyFormat; 7 | import net.kyori.adventure.translation.GlobalTranslator; 8 | import org.bukkit.ChatColor; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.Locale; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | 15 | import static net.kyori.adventure.text.Component.empty; 16 | import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection; 17 | import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.parseChar; 18 | 19 | public final class LegacyFormatUtil { 20 | private static final Map legacyMap; 21 | 22 | static { 23 | ChatColor[] values = ChatColor.values(); 24 | legacyMap = CollectionProvider.map(values.length); 25 | for (ChatColor value : values) { 26 | if (!value.isColor()) continue; 27 | 28 | char c = value.getChar(); 29 | LegacyFormat format = Objects.requireNonNull(parseChar(c)); 30 | legacyMap.put((NamedTextColor) format.color(), c); 31 | } 32 | } 33 | 34 | private LegacyFormatUtil() { 35 | } 36 | 37 | public static String limitLegacyText(String text, int limit) { 38 | if (text.length() <= limit) { 39 | return text; 40 | } 41 | 42 | int lastNotColorCharIndex = limit - 1; 43 | while (text.charAt(lastNotColorCharIndex) == LegacyComponentSerializer.SECTION_CHAR) { 44 | lastNotColorCharIndex--; 45 | } 46 | 47 | return text.substring(0, lastNotColorCharIndex + 1); 48 | } 49 | 50 | public static String serialize(@Nullable Component component, @Nullable Locale locale) { 51 | if (component == null || component == empty()) return ""; 52 | 53 | Component translated; 54 | if (locale != null) { 55 | translated = GlobalTranslator.render(component, locale); 56 | } else { 57 | translated = component; 58 | } 59 | 60 | return legacySection().serialize(translated); 61 | } 62 | 63 | public static char getChar(@Nullable NamedTextColor color) { 64 | if (color == null) return 'r'; 65 | 66 | return legacyMap.getOrDefault(color, '\0'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopScoreboardObjective.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.ComponentLike; 5 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType; 6 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore; 7 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 8 | import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static net.kyori.adventure.text.Component.empty; 16 | 17 | public class NoopScoreboardObjective implements ScoreboardObjective { 18 | private final Map scores = new HashMap<>(); 19 | private Component value = empty(); 20 | private ObjectiveRenderType renderType; 21 | private ScoreFormat defaultScoreFormat; 22 | 23 | @Override 24 | public @NotNull Component value() { 25 | return value; 26 | } 27 | 28 | @Override 29 | public @NotNull ScoreboardObjective value(@NotNull ComponentLike value) { 30 | this.value = value.asComponent(); 31 | return this; 32 | } 33 | 34 | @Override 35 | public @NotNull ObjectiveRenderType renderType() { 36 | return renderType; 37 | } 38 | 39 | @Override 40 | public @NotNull ScoreboardObjective renderType(@NotNull ObjectiveRenderType renderType) { 41 | this.renderType = renderType; 42 | return this; 43 | } 44 | 45 | @Override 46 | public @Nullable ScoreFormat defaultScoreFormat() { 47 | return defaultScoreFormat; 48 | } 49 | 50 | @Override 51 | public void defaultScoreFormat(@Nullable ScoreFormat defaultScoreFormat) { 52 | this.defaultScoreFormat = defaultScoreFormat; 53 | } 54 | 55 | @Override 56 | public void refreshProperties() { 57 | } 58 | 59 | @Override 60 | public @Nullable ObjectiveScore scoreInfo(@NotNull String entry) { 61 | return scores.get(entry); 62 | } 63 | 64 | @Override 65 | public @NotNull ScoreboardObjective score(@NotNull String entry, ObjectiveScore score) { 66 | scores.put(entry, score); 67 | return this; 68 | } 69 | 70 | @Override 71 | public @NotNull ScoreboardObjective removeScore(@NotNull String entry) { 72 | scores.remove(entry); 73 | return this; 74 | } 75 | 76 | @Override 77 | public void refreshScore(@NotNull String entry) { 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /commons/src/main/java/net/megavex/scoreboardlibrary/implementation/commons/CollectionProvider.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.commons; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.util.*; 10 | 11 | public final class CollectionProvider { 12 | private static final MethodHandle mapConstructor, setConstructor, listConstructor; 13 | 14 | static { 15 | Lookup lookup = MethodHandles.publicLookup(); 16 | MethodType parameters = MethodType.methodType(void.class, int.class); 17 | mapConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap", HashMap.class, parameters); 18 | setConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.ObjectOpenHashSet", HashSet.class, parameters); 19 | listConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.ObjectArrayList", ArrayList.class, parameters); 20 | } 21 | 22 | private CollectionProvider() { 23 | } 24 | 25 | private static MethodHandle getConstructor(Lookup lookup, String fastUtilClass, Class fallback, MethodType parameters) { 26 | Class clazz; 27 | try { 28 | clazz = Class.forName(fastUtilClass); 29 | } catch (ClassNotFoundException e) { 30 | clazz = fallback; 31 | } 32 | 33 | try { 34 | return lookup.findConstructor(clazz, parameters); 35 | } catch (NoSuchMethodException | IllegalAccessException e) { 36 | throw new ExceptionInInitializerError(e); 37 | } 38 | } 39 | 40 | public static @NotNull Map map(int capacity) { 41 | try { 42 | // noinspection unchecked 43 | return (Map) mapConstructor.invokeWithArguments(capacity); 44 | } catch (Throwable e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | public static @NotNull Set set(int capacity) { 50 | try { 51 | // noinspection unchecked 52 | return (Set) setConstructor.invokeWithArguments(capacity); 53 | } catch (Throwable e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | public static @NotNull List list(int capacity) { 59 | try { 60 | // noinspection unchecked 61 | return (List) listConstructor.invokeWithArguments(capacity); 62 | } catch (Throwable e) { 63 | throw new RuntimeException(e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/objective/SpigotObjectivePacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType; 5 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil; 10 | import org.bukkit.entity.Player; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Collection; 15 | 16 | public class SpigotObjectivePacketAdapter extends AbstractObjectivePacketAdapter { 17 | public SpigotObjectivePacketAdapter(@NotNull PacketAdapterProviderImpl packetAdapter, @NotNull ComponentProvider componentProvider, @NotNull String objectiveName) { 18 | super(packetAdapter, componentProvider, objectiveName); 19 | } 20 | 21 | @Override 22 | public void sendScore(@NotNull Collection players, @NotNull String entry, int value, @Nullable Component display, @Nullable ScoreFormat scoreFormat) { 23 | LocalePacketUtil.sendLocalePackets(sender, players, locale -> { 24 | net.minecraft.network.chat.Component nmsDisplay = display == null ? null : componentProvider.fromAdventure(display, locale); 25 | Object numberFormat = ScoreFormatConverter.convert(componentProvider, locale, scoreFormat); 26 | return createScorePacket(entry, value, nmsDisplay, numberFormat); 27 | }); 28 | } 29 | 30 | @Override 31 | public void sendProperties( 32 | @NotNull Collection players, 33 | @NotNull PropertiesPacketType packetType, 34 | @NotNull Component value, 35 | @NotNull ObjectiveRenderType renderType, 36 | @Nullable ScoreFormat scoreFormat 37 | ) { 38 | LocalePacketUtil.sendLocalePackets( 39 | sender, 40 | players, 41 | locale -> { 42 | Object numberFormat = ScoreFormatConverter.convert(componentProvider, locale, scoreFormat); 43 | return createObjectivePacket(packetType, componentProvider.fromAdventure(value, locale), renderType, numberFormat); 44 | } 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/player/LocaleListener.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.player; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerImpl; 5 | import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerTask; 6 | import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar; 7 | import net.megavex.scoreboardlibrary.implementation.sidebar.PlayerDependantLocaleSidebar; 8 | import net.megavex.scoreboardlibrary.implementation.sidebar.SidebarTask; 9 | import net.megavex.scoreboardlibrary.implementation.team.TeamManagerImpl; 10 | import net.megavex.scoreboardlibrary.implementation.team.TeamManagerTask; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.event.EventHandler; 13 | import org.bukkit.event.EventPriority; 14 | import org.bukkit.event.Listener; 15 | import org.bukkit.event.player.PlayerLocaleChangeEvent; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class LocaleListener implements Listener { 19 | private final ScoreboardLibraryImpl scoreboardLibrary; 20 | 21 | public LocaleListener(@NotNull ScoreboardLibraryImpl scoreboardLibrary) { 22 | this.scoreboardLibrary = scoreboardLibrary; 23 | } 24 | 25 | @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) 26 | public void onPlayerLocaleChanged(PlayerLocaleChangeEvent event) { 27 | Player player = event.getPlayer(); 28 | 29 | // Need to wait a tick because the locale didn't update yet 30 | scoreboardLibrary.taskScheduler().runNextTick(() -> { 31 | ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getPlayer(player); 32 | if (slPlayer != null) { 33 | TeamManagerImpl teamManager = slPlayer.teamManagerQueue().current(); 34 | if (teamManager != null) { 35 | teamManager.taskQueue().add(new TeamManagerTask.ReloadPlayer(player)); 36 | } 37 | 38 | ObjectiveManagerImpl objectiveManager = slPlayer.objectiveManagerQueue().current(); 39 | if (objectiveManager != null) { 40 | objectiveManager.taskQueue().add(new ObjectiveManagerTask.ReloadPlayer(player)); 41 | } 42 | 43 | AbstractSidebar sidebar = slPlayer.sidebarQueue().current(); 44 | if (sidebar instanceof PlayerDependantLocaleSidebar) { 45 | sidebar.taskQueue().add(new SidebarTask.ReloadPlayer(player)); 46 | } 47 | } 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /commons/src/main/java/net/megavex/scoreboardlibrary/implementation/commons/LocaleProvider.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.commons; 2 | 3 | import net.kyori.adventure.translation.Translator; 4 | import org.bukkit.entity.Player; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodType; 10 | import java.util.Locale; 11 | import java.util.function.Function; 12 | 13 | public final class LocaleProvider { 14 | private static final Locale DEFAULT_LOCALE = Locale.US; 15 | private static final Function provider = get(); 16 | 17 | public static @NotNull Locale locale(Player player) { 18 | return provider.apply(player); 19 | } 20 | 21 | private static @NotNull Function get() { 22 | MethodHandles.Lookup lookup = MethodHandles.publicLookup(); 23 | try { 24 | @SuppressWarnings("JavaLangInvokeHandleSignature") 25 | MethodHandle adventureMethod = lookup.findVirtual(Player.class, "locale", MethodType.methodType(Locale.class)); 26 | return player -> { 27 | try { 28 | return (Locale) adventureMethod.invokeExact(player); 29 | } catch (Throwable e) { 30 | throw new RuntimeException(e); 31 | } 32 | }; 33 | } catch (IllegalAccessException | NoSuchMethodException ignored) { 34 | } 35 | 36 | MethodType methodType = MethodType.methodType(String.class); 37 | try { 38 | MethodHandle legacySpigotMethod = lookup.findVirtual(Player.Spigot.class, "getLocale", methodType); 39 | return player -> { 40 | try { 41 | Locale locale = Translator.parseLocale((String) legacySpigotMethod.invokeExact(player.spigot())); 42 | return locale == null ? DEFAULT_LOCALE : locale; 43 | } catch (Throwable e) { 44 | throw new RuntimeException(e); 45 | } 46 | }; 47 | } catch (IllegalAccessException | NoSuchMethodException ignored) { 48 | } 49 | 50 | try { 51 | MethodHandle legacyMethod = lookup.findVirtual(Player.class, "getLocale", methodType); 52 | return player -> { 53 | try { 54 | @org.jetbrains.annotations.Nullable Locale locale = Translator.parseLocale((String) legacyMethod.invokeExact(player)); 55 | return locale == null ? DEFAULT_LOCALE : locale; 56 | } catch (Throwable e) { 57 | throw new RuntimeException(e); 58 | } 59 | }; 60 | } catch (IllegalAccessException | NoSuchMethodException ignored) { 61 | throw new RuntimeException("No way to get players locale found"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/team/AbstractTeamDisplayPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team; 2 | 3 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 4 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 10 | import org.bukkit.entity.Player; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.Collection; 14 | 15 | public abstract class AbstractTeamDisplayPacketAdapter implements TeamDisplayPacketAdapter { 16 | protected final PacketSender> sender; 17 | protected final String teamName; 18 | protected final ImmutableTeamProperties properties; 19 | 20 | public AbstractTeamDisplayPacketAdapter(@NotNull PacketSender> sender, @NotNull String teamName, @NotNull ImmutableTeamProperties properties) { 21 | this.sender = sender; 22 | this.teamName = teamName; 23 | this.properties = properties; 24 | } 25 | 26 | @Override 27 | public void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection players, @NotNull Collection entries) { 28 | WrapperPlayServerTeams.TeamMode peMode; 29 | switch (packetType) { 30 | case ADD: 31 | peMode = WrapperPlayServerTeams.TeamMode.ADD_ENTITIES; 32 | break; 33 | case REMOVE: 34 | peMode = WrapperPlayServerTeams.TeamMode.REMOVE_ENTITIES; 35 | break; 36 | default: 37 | throw new IllegalStateException(); 38 | } 39 | 40 | sender.sendPacket( 41 | players, 42 | new WrapperPlayServerTeams( 43 | teamName, 44 | peMode, 45 | (WrapperPlayServerTeams.ScoreBoardTeamInfo) null, 46 | entries 47 | ) 48 | ); 49 | } 50 | 51 | protected @NotNull WrapperPlayServerTeams.TeamMode mode(@NotNull PropertiesPacketType packetType) { 52 | switch (packetType) { 53 | case CREATE: 54 | return WrapperPlayServerTeams.TeamMode.CREATE; 55 | case UPDATE: 56 | return WrapperPlayServerTeams.TeamMode.UPDATE; 57 | default: 58 | throw new IllegalStateException(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/PlayerDependantLocaleSidebar.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar; 2 | 3 | import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl; 4 | import net.megavex.scoreboardlibrary.implementation.commons.LocaleProvider; 5 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.HashMap; 11 | import java.util.Locale; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.function.Consumer; 15 | 16 | public class PlayerDependantLocaleSidebar extends AbstractSidebar { 17 | private final Map playerMap = new HashMap<>(); 18 | private final Map localeMap = new HashMap<>(); 19 | 20 | public PlayerDependantLocaleSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int maxLines, @NotNull String objectiveName) { 21 | super(scoreboardLibrary, maxLines, objectiveName); 22 | } 23 | 24 | @Override 25 | public @Nullable Locale locale() { 26 | return null; 27 | } 28 | 29 | @Override 30 | protected @NotNull Set internalPlayers() { 31 | return playerMap.keySet(); 32 | } 33 | 34 | @Override 35 | protected void forEachLineHandler(@NotNull Consumer consumer) { 36 | if (localeMap != null) { 37 | for (LocaleLineHandler value : localeMap.values()) { 38 | consumer.accept(value); 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | protected @Nullable LocaleLineHandler addPlayer0(@NotNull Player player) { 45 | LocaleLineHandler sidebar = playerMap.get(player); 46 | if (sidebar != null) { 47 | return null; 48 | } 49 | 50 | Locale locale = LocaleProvider.locale(player); 51 | 52 | sidebar = localeMap.get(locale); 53 | if (sidebar != null) { 54 | playerMap.put(player, sidebar); 55 | return sidebar; 56 | } 57 | 58 | sidebar = new LocaleLineHandler(this, locale); 59 | 60 | localeMap.put(locale, sidebar); 61 | playerMap.put(player, sidebar); 62 | 63 | return sidebar; 64 | } 65 | 66 | @Override 67 | protected @Nullable LocaleLineHandler removePlayer0(@NotNull Player player) { 68 | if (playerMap == null) return null; 69 | 70 | LocaleLineHandler lineHandler = playerMap.remove(player); 71 | if (lineHandler == null) return null; 72 | 73 | if (!lineHandler.hasPlayers() && localeMap != null) { 74 | localeMap.remove(lineHandler.locale()); 75 | } 76 | 77 | return lineHandler; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/objective/PaperObjectivePacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective; 2 | 3 | import io.papermc.paper.adventure.AdventureComponent; 4 | import net.kyori.adventure.text.Component; 5 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.NativeAdventureUtil; 11 | import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket; 12 | import net.minecraft.network.protocol.game.ClientboundSetScorePacket; 13 | import org.bukkit.entity.Player; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.Collection; 18 | 19 | public class PaperObjectivePacketAdapter extends AbstractObjectivePacketAdapter { 20 | public PaperObjectivePacketAdapter(@NotNull PacketAdapterProviderImpl packetAdapter, @NotNull ComponentProvider componentProvider, @NotNull String objectiveName) { 21 | super(packetAdapter, componentProvider, objectiveName); 22 | } 23 | 24 | @Override 25 | public void sendScore(@NotNull Collection players, @NotNull String entry, int value, @Nullable Component display, @Nullable ScoreFormat scoreFormat) { 26 | net.minecraft.network.chat.Component nmsDisplay = display == null ? null : componentProvider.fromAdventure(display, null); 27 | Object numberFormat = ScoreFormatConverter.convert(componentProvider, null, scoreFormat); 28 | ClientboundSetScorePacket packet = createScorePacket(entry, value, nmsDisplay, numberFormat); 29 | sender.sendPacket(players, packet); 30 | } 31 | 32 | @Override 33 | public void sendProperties( 34 | @NotNull Collection players, 35 | @NotNull PropertiesPacketType packetType, 36 | @NotNull Component value, 37 | @NotNull ObjectiveRenderType renderType, 38 | @Nullable ScoreFormat scoreFormat 39 | ) { 40 | AdventureComponent nmsValue = NativeAdventureUtil.fromAdventureComponent(value); 41 | Object numberFormat = ScoreFormatConverter.convert(componentProvider, null, scoreFormat); 42 | ClientboundSetObjectivePacket packet = createObjectivePacket(packetType, nmsValue, renderType, numberFormat); 43 | sender.sendPacket(players, packet); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/team/WrapperPlayServerTeamsLegacy.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team; 2 | 3 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 4 | import com.github.retrooper.packetevents.protocol.packettype.PacketType; 5 | import com.github.retrooper.packetevents.util.ColorUtil; 6 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 7 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; 8 | import com.google.common.base.Preconditions; 9 | import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 11 | 12 | public class WrapperPlayServerTeamsLegacy extends PacketWrapper { 13 | private final String teamName; 14 | private final ImmutableTeamProperties properties; 15 | private final WrapperPlayServerTeams.TeamMode teamMode; 16 | 17 | public WrapperPlayServerTeamsLegacy(String teamName, ImmutableTeamProperties properties, WrapperPlayServerTeams.TeamMode teamMode) { 18 | super(PacketType.Play.Server.TEAMS); 19 | this.teamName = teamName; 20 | this.properties = properties; 21 | Preconditions.checkArgument(teamMode == WrapperPlayServerTeams.TeamMode.CREATE || teamMode == WrapperPlayServerTeams.TeamMode.UPDATE); 22 | this.teamMode = teamMode; 23 | } 24 | 25 | @Override 26 | public void write() { 27 | if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_13)) { 28 | throw new IllegalStateException("Tried to serialize legacy teams packet on 1.13+ server version"); 29 | } 30 | 31 | writeString(teamName, 16); 32 | writeByte(teamMode.ordinal()); 33 | writeString(properties.displayName()); 34 | writeString(properties.prefix()); 35 | writeString(properties.suffix()); 36 | writeByte(properties.packOptions()); 37 | if (serverVersion == ServerVersion.V_1_7_10) { 38 | writeString(NameTagVisibility.ALWAYS.key()); 39 | writeByte(15); 40 | } else { 41 | writeString(properties.nameTagVisibility().key()); 42 | if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_9)) { 43 | writeString(properties.collisionRule().key()); 44 | } 45 | writeByte(ColorUtil.getId(properties.playerColor())); 46 | } 47 | 48 | if (teamMode == WrapperPlayServerTeams.TeamMode.CREATE) { 49 | if (serverVersion == ServerVersion.V_1_7_10) { 50 | writeShort(properties.syncedEntries().size()); 51 | } else { 52 | writeVarInt(properties.syncedEntries().size()); 53 | } 54 | 55 | for (String entry : properties.syncedEntries()) { 56 | writeString(entry); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/objective/ScoreFormatConverter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.mojang.serialization.DataResult; 5 | import com.mojang.serialization.JsonOps; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors; 9 | import net.minecraft.network.chat.Style; 10 | import net.minecraft.network.chat.numbers.BlankFormat; 11 | import net.minecraft.network.chat.numbers.FixedFormat; 12 | import net.minecraft.network.chat.numbers.StyledFormat; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.lang.invoke.MethodHandle; 17 | import java.lang.invoke.MethodHandles; 18 | import java.lang.invoke.MethodType; 19 | import java.util.Locale; 20 | import java.util.Optional; 21 | 22 | import static net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson; 23 | 24 | public final class ScoreFormatConverter { 25 | private static final MethodHandle RESULT_UNWRAP_METHOD; 26 | 27 | static { 28 | try { 29 | RESULT_UNWRAP_METHOD = MethodHandles.lookup().findVirtual(DataResult.class, "result", MethodType.methodType(Optional.class)); 30 | } catch (NoSuchMethodException | IllegalAccessException e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | private ScoreFormatConverter() { 36 | } 37 | 38 | public static @Nullable Object convert(@NotNull ComponentProvider componentProvider, @Nullable Locale locale, @Nullable ScoreFormat format) { 39 | if (format == null || !PacketAccessors.IS_1_20_3_OR_ABOVE) { 40 | return null; 41 | } 42 | 43 | if (format == ScoreFormat.blank()) { 44 | return BlankFormat.INSTANCE; 45 | } else if (format instanceof ScoreFormat.Styled) { 46 | JsonElement json = gson().serializer().toJsonTree(((ScoreFormat.Styled) format).style()); 47 | Object result = Style.Serializer.CODEC.parse(JsonOps.INSTANCE, json); 48 | Style style; 49 | try { 50 | //noinspection unchecked,rawtypes 51 | style = (Style) ((Optional) RESULT_UNWRAP_METHOD.invokeExact((DataResult) result)).orElseThrow(RuntimeException::new); 52 | } catch (Throwable e) { 53 | throw new RuntimeException(e); 54 | } 55 | return new StyledFormat(style); 56 | } else if (format instanceof ScoreFormat.Fixed) { 57 | return new FixedFormat(componentProvider.fromAdventure(((ScoreFormat.Fixed) format).content(), locale)); 58 | } else { 59 | throw new IllegalArgumentException("Invalid score format: " + format); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/PacketAdapterProviderImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents; 2 | 3 | import com.github.retrooper.packetevents.PacketEvents; 4 | import com.github.retrooper.packetevents.PacketEventsAPI; 5 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 6 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team.TeamsPacketAdapterImpl; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 11 | import org.bukkit.entity.Player; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | @SuppressWarnings("unused") 15 | public class PacketAdapterProviderImpl implements PacketAdapterProvider { 16 | private final PacketEventsAPI packetEvents; 17 | private final PacketEventsSender packetSender; 18 | 19 | public PacketAdapterProviderImpl() { 20 | this.packetEvents = PacketEvents.getAPI(); 21 | if (this.packetEvents == null) { 22 | throw new IllegalStateException("PacketEvents exists in classpath but isn't loaded"); 23 | } 24 | this.packetSender = new PacketEventsSender(this.packetEvents); 25 | } 26 | 27 | @Override 28 | public @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName) { 29 | return new TeamsPacketAdapterImpl(packetSender, teamName); 30 | } 31 | 32 | @Override 33 | public @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName) { 34 | return new ObjectivePacketAdapterImpl(packetSender, packetEvents, objectiveName); 35 | } 36 | 37 | @Override 38 | public @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player) { 39 | ServerVersion serverVer = packetEvents.getServerManager().getVersion(); 40 | return serverVer.isNewerThanOrEquals(ServerVersion.V_1_13) ? LineRenderingStrategy.MODERN : LineRenderingStrategy.LEGACY; 41 | 42 | // Waiting for packetevents to fix https://github.com/retrooper/packetevents/issues/1024 43 | /* 44 | if (serverVer.isOlderThan(ServerVersion.V_1_13)) { 45 | return LineRenderingStrategy.LEGACY; 46 | } 47 | 48 | if (Bukkit.getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) { 49 | return LineRenderingStrategy.MODERN; 50 | } 51 | 52 | ClientVersion clientVer = packetEvents.getPlayerManager().getClientVersion(player); 53 | return clientVer.isNewerThanOrEquals(ClientVersion.V_1_13) ? LineRenderingStrategy.MODERN : LineRenderingStrategy.LEGACY; 54 | */ 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/objective/ObjectiveDisplaySlot.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.objective; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Represents all valid display slots that objectives can be shown in. 10 | * 11 | * @see Minecraft Wiki 12 | */ 13 | @ApiStatus.NonExtendable 14 | public interface ObjectiveDisplaySlot { 15 | static @NotNull PlayerList playerList() { 16 | return PlayerList.INSTANCE; 17 | } 18 | 19 | static @NotNull Sidebar sidebar() { 20 | return Sidebar.INSTANCE; 21 | } 22 | 23 | static @NotNull BelowName belowName() { 24 | return BelowName.INSTANCE; 25 | } 26 | 27 | /** 28 | * @since Minecraft 1.8 29 | */ 30 | static @NotNull TeamSidebar teamSidebar(@NotNull NamedTextColor teamColor) { 31 | Preconditions.checkNotNull(teamColor); 32 | return new TeamSidebar(teamColor); 33 | } 34 | 35 | class PlayerList implements ObjectiveDisplaySlot { 36 | private static final PlayerList INSTANCE = new PlayerList(); 37 | 38 | private PlayerList() { 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "PlayerList"; 44 | } 45 | } 46 | 47 | class Sidebar implements ObjectiveDisplaySlot { 48 | private static final Sidebar INSTANCE = new Sidebar(); 49 | 50 | private Sidebar() { 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Sidebar"; 56 | } 57 | } 58 | 59 | class BelowName implements ObjectiveDisplaySlot { 60 | private static final BelowName INSTANCE = new BelowName(); 61 | 62 | private BelowName() { 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "BelowName"; 68 | } 69 | } 70 | 71 | class TeamSidebar implements ObjectiveDisplaySlot { 72 | private final NamedTextColor teamColor; 73 | 74 | private TeamSidebar(@NotNull NamedTextColor teamColor) { 75 | this.teamColor = teamColor; 76 | } 77 | 78 | public @NotNull NamedTextColor teamColor() { 79 | return teamColor; 80 | } 81 | 82 | @Override 83 | public boolean equals(Object o) { 84 | if (this == o) return true; 85 | if (o == null || getClass() != o.getClass()) return false; 86 | TeamSidebar that = (TeamSidebar) o; 87 | return teamColor.equals(that.teamColor); 88 | } 89 | 90 | @Override 91 | public int hashCode() { 92 | return teamColor.hashCode(); 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | return "TeamSidebar{teamColor=" + teamColor + "}"; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/PacketEventsSender.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents; 2 | 3 | import com.github.retrooper.packetevents.PacketEventsAPI; 4 | import com.github.retrooper.packetevents.manager.server.ServerVersion; 5 | import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; 6 | import com.github.retrooper.packetevents.netty.channel.ChannelHelper; 7 | import com.github.retrooper.packetevents.protocol.player.ClientVersion; 8 | import com.github.retrooper.packetevents.protocol.player.User; 9 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 11 | import org.bukkit.entity.Player; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.Objects; 15 | 16 | public class PacketEventsSender implements PacketSender> { 17 | private final PacketEventsAPI packetEvents; 18 | 19 | public PacketEventsSender(@NotNull PacketEventsAPI packetEvents) { 20 | this.packetEvents = packetEvents; 21 | } 22 | 23 | //@SuppressWarnings("UnstableApiUsage, deprecation") 24 | @Override 25 | public void sendPacket(Player player, PacketWrapper packet) { 26 | packetEvents.getPlayerManager().sendPacket(player, packet); 27 | 28 | /* 29 | ServerVersion serverVer = packetEvents.getServerManager().getVersion(); 30 | ClientVersion clientVer = packetEvents.getPlayerManager().getClientVersion(player); 31 | if (serverVer.isOlderThan(ServerVersion.V_1_13) || clientVer.isNewerThanOrEquals(ClientVersion.V_1_13)) { 32 | packetEvents.getPlayerManager().sendPacket(player, packet); 33 | return; 34 | } 35 | 36 | // This is a hack to send packets directly skipping plugins such as ViaVersion and ViaRewind 37 | // Based on https://discord.com/channels/721686193061888071/755472595096174873/1193239879560212520 38 | // (PacketEvents Discord) 39 | 40 | User user = packetEvents.getPlayerManager().getUser(player); 41 | if (user == null) { 42 | return; 43 | } 44 | 45 | Object channel = user.getChannel(); 46 | if (!ChannelHelper.isOpen(channel)) { 47 | return; 48 | } 49 | 50 | String viaEncoder = "via-encoder"; 51 | if (ChannelHelper.getPipelineHandler(channel, viaEncoder) == null) { 52 | packetEvents.getPlayerManager().sendPacket(player, packet); 53 | return; 54 | } 55 | 56 | packet.buffer = ChannelHelper.pooledByteBuf(channel); 57 | 58 | int id = Objects.requireNonNull(packet.getPacketTypeData().getPacketType()).getId(user.getClientVersion()); 59 | ByteBufHelper.writeVarInt(packet.buffer, id); 60 | packet.setServerVersion(clientVer.toServerVersion()); 61 | packet.write(); 62 | ChannelHelper.writeAndFlushInContext(channel, viaEncoder, packet.buffer); 63 | */ 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /versions/packetevents/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/packetevents/team/AdventureTeamDisplayPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.packetevents.team; 2 | 3 | import com.github.retrooper.packetevents.wrapper.PacketWrapper; 4 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.format.NamedTextColor; 7 | import net.kyori.adventure.translation.GlobalTranslator; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 11 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil; 12 | import org.bukkit.entity.Player; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.Collection; 16 | 17 | public class AdventureTeamDisplayPacketAdapter extends AbstractTeamDisplayPacketAdapter { 18 | public AdventureTeamDisplayPacketAdapter(@NotNull PacketSender> sender, @NotNull String teamName, @NotNull ImmutableTeamProperties properties) { 19 | super(sender, teamName, properties); 20 | } 21 | 22 | @Override 23 | public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection players) { 24 | LocalePacketUtil.sendLocalePackets( 25 | sender, 26 | players, 27 | locale -> { 28 | Component displayName = GlobalTranslator.render(properties.displayName(), locale); 29 | Component prefix = GlobalTranslator.render(properties.prefix(), locale); 30 | Component suffix = GlobalTranslator.render(properties.suffix(), locale); 31 | WrapperPlayServerTeams.NameTagVisibility nameTagVisibility = WrapperPlayServerTeams.NameTagVisibility.values()[properties.nameTagVisibility().ordinal()]; 32 | WrapperPlayServerTeams.CollisionRule collisionRule = WrapperPlayServerTeams.CollisionRule.values()[properties.collisionRule().ordinal()]; 33 | NamedTextColor color = properties.playerColor() != null ? properties.playerColor() : NamedTextColor.WHITE; 34 | WrapperPlayServerTeams.OptionData optionData = WrapperPlayServerTeams.OptionData.fromValue((byte) properties.packOptions()); 35 | 36 | WrapperPlayServerTeams.ScoreBoardTeamInfo info = new WrapperPlayServerTeams.ScoreBoardTeamInfo( 37 | displayName, 38 | prefix, 39 | suffix, 40 | nameTagVisibility, 41 | collisionRule, 42 | color, 43 | optionData 44 | ); 45 | 46 | return new WrapperPlayServerTeams( 47 | teamName, 48 | mode(packetType), 49 | info, 50 | properties.syncedEntries() 51 | ); 52 | }); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/ComponentSidebarLayout.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.kyori.adventure.text.Component; 5 | import net.kyori.adventure.text.ComponentLike; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.api.sidebar.Sidebar; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import static net.kyori.adventure.text.Component.empty; 12 | 13 | /** 14 | * Represents a component sidebar layout that can be applied to a {@link Sidebar}. 15 | */ 16 | public final class ComponentSidebarLayout { 17 | private final SidebarComponent titleComponent; 18 | private final SidebarComponent linesComponent; 19 | 20 | public ComponentSidebarLayout(@NotNull SidebarComponent titleComponent, @NotNull SidebarComponent linesComponent) { 21 | Preconditions.checkNotNull(titleComponent); 22 | Preconditions.checkNotNull(linesComponent); 23 | this.titleComponent = titleComponent; 24 | this.linesComponent = linesComponent; 25 | } 26 | 27 | public @NotNull SidebarComponent titleComponent() { 28 | return titleComponent; 29 | } 30 | 31 | public @NotNull SidebarComponent linesComponent() { 32 | return linesComponent; 33 | } 34 | 35 | /** 36 | * Applies the title and line components to a sidebar. 37 | * Call this every time you want to update the sidebar. 38 | * 39 | * @param sidebar sidebar to apply title and line components to 40 | */ 41 | public void apply(@NotNull Sidebar sidebar) { 42 | Preconditions.checkNotNull(sidebar); 43 | 44 | SidebarTitleDrawable titleDrawable = new SidebarTitleDrawable(); 45 | titleComponent.draw(titleDrawable); 46 | sidebar.title(titleDrawable.title == null ? empty() : titleDrawable.title); 47 | 48 | SidebarLineDrawable linesDrawable = new SidebarLineDrawable(sidebar); 49 | linesComponent.draw(linesDrawable); 50 | 51 | for (int i = linesDrawable.index; i < Sidebar.MAX_LINES; i++) { 52 | sidebar.line(i, null); 53 | } 54 | } 55 | 56 | private static class SidebarTitleDrawable implements LineDrawable { 57 | private Component title; 58 | 59 | @Override 60 | public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) { 61 | Preconditions.checkNotNull(line); 62 | 63 | if (title == null) { 64 | title = line.asComponent(); 65 | } 66 | } 67 | } 68 | 69 | private static class SidebarLineDrawable implements LineDrawable { 70 | private final Sidebar sidebar; 71 | private int index = 0; 72 | 73 | public SidebarLineDrawable(@NotNull Sidebar sidebar) { 74 | this.sidebar = sidebar; 75 | } 76 | 77 | @Override 78 | public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) { 79 | Preconditions.checkNotNull(line); 80 | 81 | if (index < sidebar.maxLines()) { 82 | sidebar.line(index++, line, scoreFormat); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/locale/ModernLocaleLine.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.kyori.adventure.text.Component; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 8 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo; 9 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler; 10 | import org.bukkit.entity.Player; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.Collection; 14 | 15 | import static net.kyori.adventure.text.Component.empty; 16 | 17 | // Implementation for versions above 1.13 18 | public class ModernLocaleLine implements LocaleLine, ImmutableTeamProperties { 19 | private final GlobalLineInfo info; 20 | private final SidebarLineHandler handler; 21 | private final Collection entries; 22 | private final TeamDisplayPacketAdapter packetAdapter; 23 | 24 | public ModernLocaleLine(GlobalLineInfo info, SidebarLineHandler handler) { 25 | this.info = info; 26 | this.handler = handler; 27 | this.entries = ImmutableList.of(info.player()); 28 | this.packetAdapter = info.packetAdapter().createTeamDisplayAdapter(this); 29 | packetAdapter.updateTeamPackets(); 30 | } 31 | 32 | @Override 33 | public @NotNull GlobalLineInfo info() { 34 | return info; 35 | } 36 | 37 | @Override 38 | public void value(@NotNull Component renderedComponent) { 39 | } 40 | 41 | @Override 42 | public void updateTeam() { 43 | packetAdapter.updateTeamPackets(); 44 | packetAdapter.sendProperties(PropertiesPacketType.UPDATE, handler.players()); 45 | } 46 | 47 | @Override 48 | public void sendScore(@NotNull Collection players) { 49 | handler.localeLineHandler() 50 | .sidebar() 51 | .packetAdapter() 52 | .sendScore(players, info.player(), info.objectiveScore(), null, info.scoreFormat()); 53 | } 54 | 55 | @Override 56 | public void show(@NotNull Collection players) { 57 | sendScore(players); 58 | packetAdapter.sendProperties(PropertiesPacketType.CREATE, players); 59 | } 60 | 61 | @Override 62 | public void hide(@NotNull Collection players) { 63 | handler.localeLineHandler().sidebar().packetAdapter().removeScore(players, info.player()); 64 | info.packetAdapter().removeTeam(players); 65 | } 66 | 67 | @Override 68 | public @NotNull Collection syncedEntries() { 69 | return entries; 70 | } 71 | 72 | @Override 73 | public @NotNull Component displayName() { 74 | return empty(); 75 | } 76 | 77 | @Override 78 | public @NotNull Component prefix() { 79 | Component value = info.value(); 80 | return value == null ? empty() : value; 81 | } 82 | 83 | @Override 84 | public @NotNull Component suffix() { 85 | return empty(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/PacketAdapterProviderImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective.PaperObjectivePacketAdapter; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective.SpigotObjectivePacketAdapter; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team.PaperTeamsPacketAdapterImpl; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team.SpigotTeamsPacketAdapter; 11 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.PacketUtil; 12 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter; 13 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 14 | import net.minecraft.network.protocol.Packet; 15 | import org.bukkit.entity.Player; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class PacketAdapterProviderImpl implements PacketAdapterProvider, PacketSender> { 19 | private boolean isNativeAdventure; 20 | private final ComponentProvider componentProvider; 21 | 22 | public PacketAdapterProviderImpl() { 23 | try { 24 | Class.forName("io.papermc.paper.adventure.PaperAdventure"); 25 | 26 | // Hide from relocation checkers 27 | String notRelocatedPackage = "net.ky".concat("ori.adventure.text"); 28 | 29 | // The native adventure optimisations only work when the adventure library isn't relocated 30 | if (Component.class.getPackage().getName().equals(notRelocatedPackage)) { 31 | isNativeAdventure = true; 32 | } 33 | } catch (ClassNotFoundException ignored) { 34 | } 35 | 36 | this.componentProvider = new ComponentProviderImpl(isNativeAdventure); 37 | } 38 | 39 | @Override 40 | public @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName) { 41 | return isNativeAdventure 42 | ? new PaperObjectivePacketAdapter(this, componentProvider, objectiveName) 43 | : new SpigotObjectivePacketAdapter(this, componentProvider, objectiveName); 44 | } 45 | 46 | @Override 47 | public @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName) { 48 | return isNativeAdventure 49 | ? new PaperTeamsPacketAdapterImpl(this, componentProvider, teamName) 50 | : new SpigotTeamsPacketAdapter(this, componentProvider, teamName); 51 | } 52 | 53 | @Override 54 | public @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player) { 55 | return LineRenderingStrategy.MODERN; 56 | } 57 | 58 | @Override 59 | public void sendPacket(@NotNull Player player, @NotNull Packet packet) { 60 | PacketUtil.sendPacket(player, packet); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/objective/ScoreFormat.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.objective; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.kyori.adventure.text.Component; 5 | import net.kyori.adventure.text.ComponentLike; 6 | import net.kyori.adventure.text.format.Style; 7 | import net.kyori.adventure.text.format.StyleBuilderApplicable; 8 | import org.jetbrains.annotations.ApiStatus; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import static net.kyori.adventure.text.format.Style.style; 12 | 13 | /** 14 | * Represents all valid objective score formats. 15 | * 16 | * @since Minecraft 1.20.3 17 | */ 18 | @ApiStatus.NonExtendable 19 | public interface ScoreFormat { 20 | static @NotNull Blank blank() { 21 | return Blank.INSTANCE; 22 | } 23 | 24 | static @NotNull Fixed fixed(@NotNull ComponentLike content) { 25 | Preconditions.checkNotNull(content); 26 | return new Fixed(content.asComponent()); 27 | } 28 | 29 | static @NotNull Styled styled(@NotNull Style style) { 30 | Preconditions.checkNotNull(style); 31 | return new Styled(style); 32 | } 33 | 34 | static @NotNull Styled styled(@NotNull StyleBuilderApplicable @NotNull ... styleBuilderApplicables) { 35 | Preconditions.checkNotNull(styleBuilderApplicables); 36 | return new Styled(style(styleBuilderApplicables)); 37 | } 38 | 39 | class Blank implements ScoreFormat { 40 | private static final Blank INSTANCE = new Blank(); 41 | 42 | private Blank() { 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "Blank"; 48 | } 49 | } 50 | 51 | class Fixed implements ScoreFormat { 52 | private final Component content; 53 | 54 | public Fixed(@NotNull Component content) { 55 | this.content = content; 56 | } 57 | 58 | public @NotNull Component content() { 59 | return content; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | Fixed that = (Fixed) o; 67 | return content.equals(that.content); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return content.hashCode(); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "Fixed{content=" + content + "}"; 78 | } 79 | } 80 | 81 | class Styled implements ScoreFormat { 82 | private final Style style; 83 | 84 | public Styled(@NotNull Style style) { 85 | this.style = style; 86 | } 87 | 88 | public @NotNull Style style() { 89 | return style; 90 | } 91 | 92 | @Override 93 | public boolean equals(Object o) { 94 | if (this == o) return true; 95 | if (o == null || getClass() != o.getClass()) return false; 96 | Styled that = (Styled) o; 97 | return style.equals(that.style); 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | return style.hashCode(); 103 | } 104 | 105 | @Override 106 | public String toString() { 107 | return "Styled{style=" + style + "}"; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/util/PacketUtil.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util; 2 | 3 | import net.minecraft.network.protocol.Packet; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.lang.invoke.MethodHandle; 10 | import java.lang.invoke.MethodHandles; 11 | import java.lang.invoke.MethodType; 12 | import java.lang.reflect.Field; 13 | 14 | public final class PacketUtil { 15 | private static final MethodHandle GET_HANDLE; 16 | private static final MethodHandle PLAYER_CONNECTION; 17 | private static final MethodHandle SEND_PACKET; 18 | 19 | private PacketUtil() { 20 | } 21 | 22 | static { 23 | String cbPackage = Bukkit.getServer().getClass().getPackage().getName(); 24 | 25 | Class craftPlayer; 26 | try { 27 | craftPlayer = Class.forName(cbPackage + ".entity.CraftPlayer"); 28 | } catch (ClassNotFoundException e) { 29 | throw new ExceptionInInitializerError(e); 30 | } 31 | 32 | MethodHandles.Lookup lookup = MethodHandles.publicLookup(); 33 | MethodType methodType = MethodType.methodType(ServerPlayer.class); 34 | try { 35 | GET_HANDLE = lookup.findVirtual(craftPlayer, "getHandle", methodType); 36 | } catch (NoSuchMethodException | IllegalAccessException e) { 37 | throw new ExceptionInInitializerError(e); 38 | } 39 | 40 | MethodHandle playerConnection = null; 41 | for (Field field : ServerPlayer.class.getFields()) { 42 | if (field.getType() == ServerGamePacketListenerImpl.class) { 43 | try { 44 | playerConnection = lookup.unreflectGetter(field); 45 | } catch (IllegalAccessException e) { 46 | throw new ExceptionInInitializerError(e); 47 | } 48 | } 49 | } 50 | 51 | if (playerConnection == null) { 52 | throw new ExceptionInInitializerError("failed to find player connection field"); 53 | } 54 | PLAYER_CONNECTION = playerConnection; 55 | 56 | MethodType sendMethodType = MethodType.methodType(void.class, Packet.class); 57 | MethodHandle sendPacket = null; 58 | 59 | String[] sendPacketNames = {"a", "sendPacket", "b", "send"}; 60 | for (String name : sendPacketNames) { 61 | try { 62 | sendPacket = lookup.findVirtual(ServerGamePacketListenerImpl.class, name, sendMethodType); 63 | } catch (NoSuchMethodException ignored) { 64 | } catch (IllegalAccessException e) { 65 | throw new ExceptionInInitializerError(e); 66 | } 67 | } 68 | 69 | if (sendPacket == null) { 70 | throw new ExceptionInInitializerError(new RuntimeException("Couldn't find send packet method")); 71 | } 72 | 73 | SEND_PACKET = sendPacket; 74 | } 75 | 76 | public static void sendPacket(Player player, Packet packet) { 77 | try { 78 | ServerPlayer handle = (ServerPlayer) GET_HANDLE.invoke(player); 79 | ServerGamePacketListenerImpl connection = (ServerGamePacketListenerImpl) PLAYER_CONNECTION.invoke(handle); 80 | SEND_PACKET.invoke(connection, packet); 81 | } catch (Throwable e) { 82 | throw new IllegalStateException("couldn't send packet to player", e); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/objective/ObjectiveManager.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.objective; 2 | 3 | import net.megavex.scoreboardlibrary.api.ScoreboardLibrary; 4 | import org.bukkit.entity.Player; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * Represents a group of {@link ScoreboardObjective}s. 11 | * To get an instance of this interface, use {@link ScoreboardLibrary#createObjectiveManager()} 12 | * Note: this interface is not thread-safe, meaning you can only use it from one thread at a time, 13 | * although it does not have to be the main thread. 14 | */ 15 | public interface ObjectiveManager { 16 | /** 17 | * Creates an objective with a name if one doesn't already exist and returns it. 18 | * 19 | * @param name name of the objective 20 | * @return objective 21 | */ 22 | @NotNull ScoreboardObjective create(@NotNull String name); 23 | 24 | /** 25 | * Removes an objective. 26 | * 27 | * @param objective objective to remove 28 | */ 29 | void remove(@NotNull ScoreboardObjective objective); 30 | 31 | /** 32 | * Updates which objective is shown in a display slot. 33 | * 34 | * @param displaySlot display slot value to show objective in 35 | * @param objective objective to display in that display slot 36 | */ 37 | void display(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjective objective); 38 | 39 | /** 40 | * @return unmodifiable collection of viewers in this ObjectiveManager 41 | * @see #addPlayer 42 | * @see #removePlayer 43 | */ 44 | @NotNull Collection players(); 45 | 46 | /** 47 | * Adds a viewer to this ObjectiveManager. 48 | * Note that a player can only see a single ObjectiveManager at a time. 49 | * The ObjectiveManager will internally be added to a queue for this player who 50 | * will start seeing it once they are removed from all previous ObjectiveManagers. 51 | * 52 | * @param player player to add 53 | * @return whether the player was added 54 | */ 55 | boolean addPlayer(@NotNull Player player); 56 | 57 | /** 58 | * Adds multiple viewers to this ObjectiveManager. 59 | * 60 | * @param players viewers to add 61 | * @see #addPlayer 62 | */ 63 | default void addPlayers(@NotNull Collection players) { 64 | for (Player player : players) { 65 | addPlayer(player); 66 | } 67 | } 68 | 69 | /** 70 | * Removes a viewer from this ObjectiveManager. 71 | * 72 | * @param player viewer to remove 73 | * @return whether the viewer was removed 74 | */ 75 | boolean removePlayer(@NotNull Player player); 76 | 77 | /** 78 | * Removes multiple viewers from this ObjectiveManager 79 | * 80 | * @param players viewers to remove 81 | */ 82 | default void removePlayers(@NotNull Collection players) { 83 | for (Player player : players) { 84 | removePlayer(player); 85 | } 86 | } 87 | 88 | /** 89 | * Closes this ObjectiveManager. 90 | * This must be called once you no longer need this ObjectiveManager to prevent a memory leak. 91 | */ 92 | void close(); 93 | 94 | /** 95 | * @return whether this ObjectiveManager is closed 96 | * @see #close 97 | */ 98 | boolean closed(); 99 | } 100 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/team/SpigotTeamsPacketAdapter.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.kyori.adventure.text.Component; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 11 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors; 12 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil; 13 | import net.minecraft.network.protocol.Packet; 14 | import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; 15 | import org.bukkit.entity.Player; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.UnknownNullability; 18 | 19 | import java.util.Collection; 20 | import java.util.Locale; 21 | 22 | public class SpigotTeamsPacketAdapter extends AbstractTeamsPacketAdapterImpl { 23 | public SpigotTeamsPacketAdapter(@NotNull PacketSender> sender, @NotNull ComponentProvider componentProvider, @NotNull String teamName) { 24 | super(sender, componentProvider, teamName); 25 | } 26 | 27 | @Override 28 | public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties) { 29 | return new TeamDisplayPacketAdapterImpl(properties); 30 | } 31 | 32 | private class TeamDisplayPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl.TeamDisplayPacketAdapterImpl { 33 | public TeamDisplayPacketAdapterImpl(ImmutableTeamProperties properties) { 34 | super(properties); 35 | } 36 | 37 | @Override 38 | public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection players) { 39 | Collection entries = ImmutableList.copyOf(properties.syncedEntries()); 40 | LocalePacketUtil.sendLocalePackets( 41 | sender, 42 | players, 43 | locale -> { 44 | ClientboundSetPlayerTeamPacket.@NotNull Parameters parameters = PacketAccessors.PARAMETERS_CONSTRUCTOR.invoke(); 45 | fillParameters(parameters, locale); 46 | return createTeamsPacket(TeamConstants.mode(packetType), teamName, parameters, entries); 47 | } 48 | ); 49 | } 50 | 51 | @Override 52 | protected void fillParameters(ClientboundSetPlayerTeamPacket.@NotNull Parameters parameters, @UnknownNullability Locale locale) { 53 | super.fillParameters(parameters, locale); 54 | 55 | PacketAccessors.DISPLAY_NAME_FIELD.set(parameters, componentProvider.fromAdventure(properties.displayName(), locale)); 56 | PacketAccessors.PREFIX_FIELD.set(parameters, componentProvider.fromAdventure(properties.prefix(), locale)); 57 | PacketAccessors.SUFFIX_FIELD.set(parameters, componentProvider.fromAdventure(properties.suffix(), locale)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /api/src/test/java/net/megavex/scoreboardlibrary/api/sidebar/component/ComponentSidebarLayoutTest.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary; 5 | import net.megavex.scoreboardlibrary.api.sidebar.Sidebar; 6 | import net.megavex.scoreboardlibrary.api.sidebar.component.animation.CollectionSidebarAnimation; 7 | import net.megavex.scoreboardlibrary.api.sidebar.component.animation.SidebarAnimation; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.Arrays; 11 | 12 | import static net.kyori.adventure.text.Component.text; 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | class ComponentSidebarLayoutTest { 16 | private final Sidebar sidebar = new NoopScoreboardLibrary().createSidebar(); 17 | 18 | @Test 19 | void maxLines() { 20 | SidebarComponent.Builder builder = SidebarComponent.builder(); 21 | for (int i = 0; i < Sidebar.MAX_LINES + 1; i++) { 22 | builder.addComponent(SidebarComponent.staticLine(text(i))); 23 | } 24 | 25 | ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> { 26 | }, builder.build()); 27 | 28 | componentSidebar.apply(sidebar); 29 | 30 | for (int i = 0; i < Sidebar.MAX_LINES; i++) { 31 | assertNotNull(sidebar.line(i)); 32 | } 33 | } 34 | 35 | @Test 36 | void titleComponent() { 37 | Component title = text("title"); 38 | ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(SidebarComponent.staticLine(title), drawable -> { 39 | }); 40 | componentSidebar.apply(sidebar); 41 | assertEquals(title, sidebar.title()); 42 | } 43 | 44 | @Test 45 | void animatedLines() { 46 | SidebarAnimation animation = new CollectionSidebarAnimation<>(Arrays.asList(text("frame 1"), text("frame 2"))); 47 | SidebarComponent lines = SidebarComponent.builder().addAnimatedLine(animation).build(); 48 | ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> { 49 | }, lines); 50 | 51 | componentSidebar.apply(sidebar); 52 | assertEquals(animation.currentFrame(), sidebar.line(0)); 53 | 54 | animation.nextFrame(); 55 | componentSidebar.apply(sidebar); 56 | assertEquals(animation.currentFrame(), sidebar.line(0)); 57 | } 58 | 59 | @Test 60 | void animatedComponents() { 61 | Component frame1Line = text("frame with one line"); 62 | Component frame2Line1 = text("frame with"); 63 | Component frame2Line2 = text("two lines"); 64 | 65 | SidebarComponent frame1 = SidebarComponent.staticLine(frame1Line); 66 | SidebarComponent frame2 = drawable -> { 67 | drawable.drawLine(frame2Line1); 68 | drawable.drawLine(frame2Line2); 69 | }; 70 | 71 | SidebarAnimation animation = new CollectionSidebarAnimation<>(Arrays.asList(frame1, frame2)); 72 | SidebarComponent lines = SidebarComponent.builder().addAnimatedComponent(animation).build(); 73 | ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> { 74 | }, lines); 75 | 76 | componentSidebar.apply(sidebar); 77 | assertEquals(frame1Line, sidebar.line(0)); 78 | assertNull(sidebar.line(1)); 79 | 80 | animation.nextFrame(); 81 | componentSidebar.apply(sidebar); 82 | assertEquals(frame2Line1, sidebar.line(0)); 83 | assertEquals(frame2Line2, sidebar.line(1)); 84 | 85 | animation.nextFrame(); 86 | componentSidebar.apply(sidebar); 87 | assertEquals(frame1Line, sidebar.line(0)); 88 | assertNull(sidebar.line(1)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopSidebar.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.kyori.adventure.text.Component; 5 | import net.kyori.adventure.text.ComponentLike; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.api.sidebar.Sidebar; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.jetbrains.annotations.Range; 12 | 13 | import java.util.*; 14 | 15 | import static net.kyori.adventure.text.Component.empty; 16 | 17 | class NoopSidebar implements Sidebar { 18 | private final Set players = new HashSet<>(); 19 | private final int maxLines; 20 | private final String objectiveName; 21 | private final Locale locale; 22 | private final Component[] lines; 23 | private Component title = empty(); 24 | private boolean closed; 25 | 26 | NoopSidebar(int maxLines, String objectiveName, @Nullable Locale locale) { 27 | this.maxLines = maxLines; 28 | this.locale = locale; 29 | this.objectiveName = objectiveName; 30 | this.lines = new Component[maxLines]; 31 | } 32 | 33 | @Override 34 | public void close() { 35 | closed = true; 36 | } 37 | 38 | @Override 39 | public boolean closed() { 40 | return closed; 41 | } 42 | 43 | @Override 44 | public @NotNull Collection players() { 45 | return closed ? Collections.emptySet() : Collections.unmodifiableSet(players); 46 | } 47 | 48 | @Override 49 | public boolean addPlayer(@NotNull Player player) { 50 | Preconditions.checkNotNull(player); 51 | checkClosed(); 52 | return players.add(player); 53 | } 54 | 55 | @Override 56 | public boolean removePlayer(@NotNull Player player) { 57 | Preconditions.checkNotNull(player); 58 | checkClosed(); 59 | return players.remove(player); 60 | } 61 | 62 | @Override 63 | public @Range(from = 1, to = MAX_LINES) int maxLines() { 64 | return maxLines; 65 | } 66 | 67 | @Override 68 | public @NotNull String objectiveName() { 69 | return objectiveName; 70 | } 71 | 72 | @Override 73 | public @Nullable Locale locale() { 74 | return locale; 75 | } 76 | 77 | @Override 78 | public void line(int index, @Nullable ComponentLike value, @Nullable ScoreFormat scoreFormat) { 79 | checkLineBounds(index); 80 | checkClosed(); 81 | lines[index] = value == null ? null : value.asComponent(); 82 | } 83 | 84 | @Override 85 | public void refreshLine(@Range(from = 0, to = Integer.MAX_VALUE - 1) final int index) { 86 | 87 | } 88 | @Override 89 | public @Nullable Component line(int line) { 90 | checkLineBounds(line); 91 | checkClosed(); 92 | return lines[line]; 93 | } 94 | 95 | @Override 96 | public @NotNull Component title() { 97 | return title; 98 | } 99 | 100 | @Override 101 | public void title(@NotNull ComponentLike title) { 102 | Preconditions.checkNotNull(title); 103 | checkClosed(); 104 | this.title = title.asComponent(); 105 | } 106 | 107 | @Override 108 | public void refreshTitle() { 109 | 110 | } 111 | 112 | private void checkClosed() { 113 | if (closed) { 114 | throw new IllegalStateException("NoopSidebar is closed"); 115 | } 116 | } 117 | 118 | private void checkLineBounds(int line) { 119 | if (line >= maxLines || line < 0) { 120 | throw new IndexOutOfBoundsException("invalid line " + line); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/ComponentProviderImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.mojang.serialization.JsonOps; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.translation.GlobalTranslator; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.NativeAdventureUtil; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.RegistryUtil; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil; 10 | import net.minecraft.network.chat.ComponentSerialization; 11 | import net.minecraft.network.chat.MutableComponent; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.lang.invoke.MethodHandle; 16 | import java.lang.invoke.MethodHandles; 17 | import java.lang.reflect.Method; 18 | import java.util.Locale; 19 | 20 | import static net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson; 21 | 22 | public class ComponentProviderImpl implements ComponentProvider { 23 | private static final MethodHandle FROM_JSON_METHOD; 24 | 25 | static { 26 | if (!PacketAccessors.IS_1_21_6_OR_ABOVE) { 27 | Class serializerClass = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.Component$Serializer", "net.minecraft.network.chat.IChatBaseComponent$ChatSerializer"); 28 | MethodHandle handle = null; 29 | for (Method method : serializerClass.getMethods()) { 30 | if (method.getReturnType() == MutableComponent.class && 31 | method.getParameterCount() >= 1 && 32 | method.getParameterCount() <= 2 && 33 | method.getParameterTypes()[0] == JsonElement.class 34 | ) { 35 | try { 36 | handle = MethodHandles.lookup().unreflect(method); 37 | break; 38 | } catch (IllegalAccessException e) { 39 | throw new ExceptionInInitializerError(e); 40 | } 41 | } 42 | } 43 | 44 | if (handle == null) { 45 | throw new ExceptionInInitializerError("failed to find chat component fromJson method"); 46 | } 47 | 48 | FROM_JSON_METHOD = handle; 49 | } else { 50 | FROM_JSON_METHOD = null; 51 | } 52 | } 53 | 54 | private final boolean isNativeAdventure; 55 | 56 | public ComponentProviderImpl(boolean isNativeAdventure) { 57 | this.isNativeAdventure = isNativeAdventure; 58 | } 59 | 60 | @Override 61 | public net.minecraft.network.chat.@NotNull Component fromAdventure(@NotNull Component adventure, @Nullable Locale locale) { 62 | if (isNativeAdventure) { 63 | return NativeAdventureUtil.fromAdventureComponent(adventure); 64 | } 65 | 66 | Component translated = adventure; 67 | if (locale != null) { 68 | translated = GlobalTranslator.render(adventure, locale); 69 | } 70 | JsonElement json = gson().serializeToTree(translated); 71 | 72 | if (FROM_JSON_METHOD == null) { 73 | // 1.21.6+ 74 | return ComponentSerialization.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow(); 75 | } 76 | 77 | Object[] args; 78 | if (PacketAccessors.IS_1_20_5_OR_ABOVE) { 79 | args = new Object[]{json, RegistryUtil.MINECRAFT_REGISTRY}; 80 | } else { 81 | args = new Object[]{json}; 82 | } 83 | 84 | try { 85 | return (net.minecraft.network.chat.Component) FROM_JSON_METHOD.invokeWithArguments(args); 86 | } catch (Throwable e) { 87 | throw new RuntimeException(e); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/sidebar/component/SidebarComponent.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.sidebar.component; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import net.kyori.adventure.text.ComponentLike; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 7 | import net.megavex.scoreboardlibrary.api.sidebar.component.animation.SidebarAnimation; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.function.Supplier; 13 | 14 | import static net.kyori.adventure.text.Component.empty; 15 | 16 | public interface SidebarComponent { 17 | static @NotNull SidebarComponent staticLine(@NotNull ComponentLike line) { 18 | Preconditions.checkNotNull(line); 19 | return drawable -> drawable.drawLine(line); 20 | } 21 | 22 | static @NotNull SidebarComponent staticLine(@NotNull ComponentLike line, @NotNull ScoreFormat scoreFormat) { 23 | Preconditions.checkNotNull(line); 24 | return drawable -> drawable.drawLine(line, scoreFormat); 25 | } 26 | 27 | static @NotNull SidebarComponent blankLine() { 28 | return drawable -> drawable.drawLine(empty()); 29 | } 30 | 31 | static @NotNull SidebarComponent dynamicLine(@NotNull Supplier lineSupplier) { 32 | return drawable -> drawable.drawLine(lineSupplier.get()); 33 | } 34 | 35 | static @NotNull SidebarComponent animatedLine(@NotNull SidebarAnimation animation) { 36 | Preconditions.checkNotNull(animation); 37 | return drawable -> drawable.drawLine(animation.currentFrame()); 38 | } 39 | 40 | static @NotNull SidebarComponent animatedComponent(@NotNull SidebarAnimation animation) { 41 | Preconditions.checkNotNull(animation); 42 | return drawable -> animation.currentFrame().draw(drawable); 43 | } 44 | 45 | static @NotNull Builder builder() { 46 | return new Builder(); 47 | } 48 | 49 | void draw(@NotNull LineDrawable drawable); 50 | 51 | final class Builder { 52 | private final List children = new ArrayList<>(4); 53 | 54 | private Builder() { 55 | } 56 | 57 | public @NotNull Builder addComponent(@NotNull SidebarComponent component) { 58 | Preconditions.checkNotNull(component); 59 | children.add(component); 60 | return this; 61 | } 62 | 63 | public @NotNull Builder addStaticLine(@NotNull ComponentLike line) { 64 | return addComponent(SidebarComponent.staticLine(line)); 65 | } 66 | 67 | public @NotNull Builder addStaticLine(@NotNull ComponentLike line, @NotNull ScoreFormat scoreFormat) { 68 | return addComponent(SidebarComponent.staticLine(line, scoreFormat)); 69 | } 70 | 71 | public @NotNull Builder addBlankLine() { 72 | return addComponent(SidebarComponent.blankLine()); 73 | } 74 | 75 | public @NotNull Builder addDynamicLine(@NotNull Supplier lineSupplier) { 76 | return addComponent(SidebarComponent.dynamicLine(lineSupplier)); 77 | } 78 | 79 | public @NotNull Builder addAnimatedLine(@NotNull SidebarAnimation animation) { 80 | return addComponent(SidebarComponent.animatedLine(animation)); 81 | } 82 | 83 | public @NotNull Builder addAnimatedComponent(@NotNull SidebarAnimation animation) { 84 | return addComponent(SidebarComponent.animatedComponent(animation)); 85 | } 86 | 87 | public @NotNull SidebarComponent build() { 88 | ImmutableList children = ImmutableList.copyOf(this.children); 89 | return drawable -> { 90 | for (SidebarComponent child : children) { 91 | child.draw(drawable); 92 | } 93 | }; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/LocaleLineHandler.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 5 | import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Collections; 10 | import java.util.Locale; 11 | 12 | public class LocaleLineHandler { 13 | private final AbstractSidebar sidebar; 14 | private final Locale locale; 15 | private final SidebarLineHandler[] lineHandlers; 16 | 17 | public LocaleLineHandler(@NotNull AbstractSidebar sidebar, @NotNull Locale locale) { 18 | this.sidebar = sidebar; 19 | this.locale = locale; 20 | this.lineHandlers = new SidebarLineHandler[LineRenderingStrategy.values().length]; 21 | } 22 | 23 | public @NotNull AbstractSidebar sidebar() { 24 | return sidebar; 25 | } 26 | 27 | public @NotNull Locale locale() { 28 | return locale; 29 | } 30 | 31 | public boolean hasPlayers() { 32 | for (SidebarLineHandler lineHandler : lineHandlers) { 33 | if (lineHandler != null && !lineHandler.players().isEmpty()) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | public void addPlayer(@NotNull Player player) { 41 | LineRenderingStrategy strategy = sidebar.scoreboardLibrary().packetAdapter().lineRenderingStrategy(player); 42 | lineHandler(strategy).players().add(player); 43 | } 44 | 45 | public void removePlayer(@NotNull Player player) { 46 | for (SidebarLineHandler lineHandler : lineHandlers) { 47 | if (lineHandler != null && lineHandler.players().remove(player)) { 48 | return; 49 | } 50 | } 51 | } 52 | 53 | public @NotNull SidebarLineHandler lineHandler(@NotNull LineRenderingStrategy lineType) { 54 | SidebarLineHandler lineHandler = lineHandlers[lineType.ordinal()]; 55 | if (lineHandler == null) { 56 | lineHandler = lineHandlers[lineType.ordinal()] = new SidebarLineHandler(lineType, this); 57 | } 58 | return lineHandler; 59 | } 60 | 61 | public void updateScoreFormat(int lineIndex) { 62 | SidebarLineHandler lineHandler = lineHandlers[LineRenderingStrategy.MODERN.ordinal()]; 63 | if (lineHandler != null) { 64 | lineHandler.updateScore(lineIndex); 65 | } 66 | } 67 | 68 | public void updateLine(int lineIndex, Component renderedValue) { 69 | for (SidebarLineHandler lineHandler : lineHandlers) { 70 | if (lineHandler != null) { 71 | lineHandler.setLine(lineIndex, renderedValue); 72 | } 73 | } 74 | } 75 | 76 | public void updateScores() { 77 | for (SidebarLineHandler lineHandler : lineHandlers) { 78 | if (lineHandler != null) { 79 | lineHandler.updateScores(); 80 | } 81 | } 82 | } 83 | 84 | public void show(Player player) { 85 | for (SidebarLineHandler lineHandler : lineHandlers) { 86 | if (lineHandler != null && lineHandler.players().contains(player)) { 87 | lineHandler.show(Collections.singleton(player)); 88 | return; 89 | } 90 | } 91 | } 92 | 93 | public void hide(Player player) { 94 | for (SidebarLineHandler lineHandler : lineHandlers) { 95 | if (lineHandler != null && lineHandler.players().contains(player)) { 96 | lineHandler.hide(Collections.singleton(player)); 97 | return; 98 | } 99 | } 100 | } 101 | 102 | public void hide() { 103 | for (SidebarLineHandler lineHandler : lineHandlers) { 104 | if (lineHandler != null) { 105 | lineHandler.hide(lineHandler.players()); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/sidebar/line/SidebarLineHandler.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.sidebar.line; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.translation.GlobalTranslator; 5 | import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider; 6 | import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy; 7 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LegacyLocaleLine; 8 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LocaleLine; 9 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.ModernLocaleLine; 10 | import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.PostModernLocaleLine; 11 | import org.bukkit.entity.Player; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.Collection; 15 | import java.util.Set; 16 | 17 | public class SidebarLineHandler { 18 | private final LineRenderingStrategy strategy; 19 | private final LocaleLineHandler localeLineHandler; 20 | private final Set players = CollectionProvider.set(1); 21 | private final LocaleLine[] lines; 22 | 23 | public SidebarLineHandler(@NotNull LineRenderingStrategy strategy, @NotNull LocaleLineHandler localeLineHandler) { 24 | this.strategy = strategy; 25 | this.localeLineHandler = localeLineHandler; 26 | this.lines = new LocaleLine[localeLineHandler.sidebar().maxLines()]; 27 | 28 | for (GlobalLineInfo line : localeLineHandler.sidebar().lines()) { 29 | if (line != null) { 30 | Component value = line.value(); 31 | if (value != null) { 32 | setLine(line.line(), GlobalTranslator.render(value, localeLineHandler.locale())); 33 | } 34 | } 35 | } 36 | } 37 | 38 | public @NotNull LocaleLineHandler localeLineHandler() { 39 | return localeLineHandler; 40 | } 41 | 42 | public @NotNull Set players() { 43 | return players; 44 | } 45 | 46 | public void updateScores() { 47 | for (LocaleLine line : lines) { 48 | if (line != null && line.info().updateScore()) { 49 | line.sendScore(players); 50 | } 51 | } 52 | } 53 | 54 | public void updateScore(int line) { 55 | LocaleLine localeLine = lines[line]; 56 | localeLine.sendScore(players); 57 | } 58 | 59 | public void setLine(int line, Component renderedLine) { 60 | LocaleLine localeLine = lines[line]; 61 | if (renderedLine == null && localeLine == null) { 62 | return; 63 | } 64 | 65 | boolean newlyCreated = false; 66 | if (localeLine == null) { 67 | lines[line] = localeLine = createLine(strategy, localeLineHandler.sidebar().lines()[line]); 68 | newlyCreated = true; 69 | } 70 | 71 | if (renderedLine == null) { 72 | lines[line] = null; 73 | } else { 74 | localeLine.value(renderedLine); 75 | } 76 | 77 | if (renderedLine == null) { 78 | localeLine.hide(players); 79 | } else if (newlyCreated) { 80 | localeLine.resetOldPlayer(); 81 | localeLine.show(players); 82 | } else { 83 | localeLine.updateTeam(); 84 | } 85 | } 86 | 87 | public void show(Collection players) { 88 | for (LocaleLine line : lines) { 89 | if (line != null) { 90 | line.show(players); 91 | } 92 | } 93 | } 94 | 95 | public void hide(Collection players) { 96 | for (LocaleLine line : lines) { 97 | if (line != null) { 98 | line.hide(players); 99 | } 100 | } 101 | } 102 | 103 | private @NotNull LocaleLine createLine(@NotNull LineRenderingStrategy strategy, @NotNull GlobalLineInfo line) { 104 | switch (strategy) { 105 | case LEGACY: 106 | return new LegacyLocaleLine(line, this); 107 | case MODERN: 108 | return new ModernLocaleLine(line, this); 109 | case POST_MODERN: 110 | return new PostModernLocaleLine(line, this); 111 | default: 112 | throw new IllegalArgumentException(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/objective/ObjectiveManagerTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.objective; 2 | 3 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore; 5 | import org.bukkit.entity.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public class ObjectiveManagerTask { 10 | private ObjectiveManagerTask() { 11 | } 12 | 13 | public static final class Close extends ObjectiveManagerTask { 14 | public static final Close INSTANCE = new Close(); 15 | 16 | private Close() { 17 | } 18 | } 19 | 20 | public static final class AddPlayer extends ObjectiveManagerTask { 21 | private final Player player; 22 | 23 | public AddPlayer(@NotNull Player player) { 24 | this.player = player; 25 | } 26 | 27 | public @NotNull Player player() { 28 | return player; 29 | } 30 | } 31 | 32 | public static final class RemovePlayer extends ObjectiveManagerTask { 33 | private final Player player; 34 | 35 | public RemovePlayer(@NotNull Player player) { 36 | this.player = player; 37 | } 38 | 39 | public @NotNull Player player() { 40 | return player; 41 | } 42 | } 43 | 44 | public static final class ReloadPlayer extends ObjectiveManagerTask { 45 | private final Player player; 46 | 47 | public ReloadPlayer(@NotNull Player player) { 48 | this.player = player; 49 | } 50 | 51 | public @NotNull Player player() { 52 | return player; 53 | } 54 | } 55 | 56 | public static final class AddObjective extends ObjectiveManagerTask { 57 | private final ScoreboardObjectiveImpl objective; 58 | 59 | public AddObjective(@NotNull ScoreboardObjectiveImpl team) { 60 | this.objective = team; 61 | } 62 | 63 | public @NotNull ScoreboardObjectiveImpl objective() { 64 | return objective; 65 | } 66 | } 67 | 68 | public static final class RemoveObjective extends ObjectiveManagerTask { 69 | private final ScoreboardObjectiveImpl objective; 70 | 71 | public RemoveObjective(@NotNull ScoreboardObjectiveImpl team) { 72 | this.objective = team; 73 | } 74 | 75 | public @NotNull ScoreboardObjectiveImpl objective() { 76 | return objective; 77 | } 78 | } 79 | 80 | public static final class UpdateObjective extends ObjectiveManagerTask { 81 | private final ScoreboardObjectiveImpl objective; 82 | 83 | public UpdateObjective(@NotNull ScoreboardObjectiveImpl objective) { 84 | this.objective = objective; 85 | } 86 | 87 | public @NotNull ScoreboardObjectiveImpl objective() { 88 | return objective; 89 | } 90 | } 91 | 92 | public static final class UpdateScore extends ObjectiveManagerTask { 93 | private final ScoreboardObjectiveImpl objective; 94 | private final String entry; 95 | private final ObjectiveScore score; 96 | 97 | public UpdateScore(@NotNull ScoreboardObjectiveImpl objective, @NotNull String entry, @Nullable ObjectiveScore score) { 98 | this.objective = objective; 99 | this.entry = entry; 100 | this.score = score; 101 | } 102 | 103 | public @NotNull ScoreboardObjectiveImpl objective() { 104 | return objective; 105 | } 106 | 107 | public @NotNull String entry() { 108 | return entry; 109 | } 110 | 111 | public @Nullable ObjectiveScore score() { 112 | return score; 113 | } 114 | } 115 | 116 | public static final class UpdateDisplaySlot extends ObjectiveManagerTask { 117 | private final ObjectiveDisplaySlot displaySlot; 118 | private final ScoreboardObjectiveImpl objective; 119 | 120 | public UpdateDisplaySlot(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjectiveImpl objective) { 121 | this.displaySlot = displaySlot; 122 | this.objective = objective; 123 | } 124 | 125 | public @NotNull ObjectiveDisplaySlot displaySlot() { 126 | return displaySlot; 127 | } 128 | 129 | public @NotNull ScoreboardObjectiveImpl objective() { 130 | return objective; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/scheduler/FoliaTaskScheduler.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.scheduler; 2 | 3 | import net.kyori.adventure.util.Ticks; 4 | import org.bukkit.Server; 5 | import org.bukkit.plugin.Plugin; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.lang.invoke.MethodHandle; 9 | import java.lang.invoke.MethodHandles; 10 | import java.lang.invoke.MethodType; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.function.Consumer; 13 | 14 | class FoliaTaskScheduler implements TaskScheduler { 15 | private static final Class asyncSchedulerClass; 16 | private static final Class scheduledTaskClass; 17 | private static final Class cancelledStateClass; 18 | private static final MethodHandle getAsyncSchedulerMethod; 19 | private static final MethodHandle cancelScheduledTaskMethod; 20 | private static final MethodHandle runAtFixedRateMethod; 21 | private static final MethodHandle runDelayedMethod; 22 | 23 | static { 24 | try { 25 | asyncSchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); 26 | scheduledTaskClass = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); 27 | cancelledStateClass = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask$CancelledState"); 28 | 29 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 30 | getAsyncSchedulerMethod = lookup.findVirtual( 31 | Server.class, 32 | "getAsyncScheduler", 33 | MethodType.methodType(asyncSchedulerClass) 34 | ); 35 | 36 | cancelScheduledTaskMethod = lookup.findVirtual(scheduledTaskClass, "cancel", MethodType.methodType(cancelledStateClass)); 37 | 38 | runAtFixedRateMethod = lookup.findVirtual( 39 | asyncSchedulerClass, 40 | "runAtFixedRate", 41 | MethodType.methodType( 42 | scheduledTaskClass, 43 | Plugin.class, 44 | Consumer.class, 45 | long.class, 46 | long.class, 47 | TimeUnit.class 48 | ) 49 | ); 50 | 51 | runDelayedMethod = lookup.findVirtual( 52 | asyncSchedulerClass, 53 | "runDelayed", 54 | MethodType.methodType( 55 | scheduledTaskClass, 56 | Plugin.class, 57 | Consumer.class, 58 | long.class, 59 | TimeUnit.class 60 | ) 61 | ); 62 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { 63 | throw new ExceptionInInitializerError(e); 64 | } 65 | } 66 | 67 | private final Plugin plugin; 68 | private final Object asyncScheduler; 69 | 70 | public FoliaTaskScheduler(@NotNull Plugin plugin) { 71 | this.plugin = plugin; 72 | try { 73 | this.asyncScheduler = getAsyncSchedulerMethod.invoke(plugin.getServer()); 74 | } catch (Throwable e) { 75 | throw new RuntimeException("couldn't get async scheduler", e); 76 | } 77 | } 78 | 79 | @Override 80 | public @NotNull RunningTask runEveryTick(@NotNull Runnable runnable) { 81 | Object scheduledTask; 82 | try { 83 | Consumer task = t -> runnable.run(); 84 | scheduledTask = runAtFixedRateMethod.invoke( 85 | asyncScheduler, 86 | plugin, 87 | task, 88 | Ticks.SINGLE_TICK_DURATION_MS, 89 | Ticks.SINGLE_TICK_DURATION_MS, 90 | TimeUnit.MILLISECONDS 91 | ); 92 | } catch (Throwable e) { 93 | throw new RuntimeException("couldn't schedule repeating task", e); 94 | } 95 | 96 | return () -> { 97 | try { 98 | cancelScheduledTaskMethod.invoke(scheduledTask); 99 | } catch (Throwable e) { 100 | throw new RuntimeException("couldn't cancel scheduled task", e); 101 | } 102 | }; 103 | } 104 | 105 | @Override 106 | public void runNextTick(@NotNull Runnable runnable) { 107 | Consumer task = t -> runnable.run(); 108 | try { 109 | runDelayedMethod.invoke(asyncScheduler, plugin, task, Ticks.SINGLE_TICK_DURATION_MS, TimeUnit.MILLISECONDS); 110 | } catch (Throwable e) { 111 | throw new RuntimeException("couldn't schedule delayed task", e); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopTeamDisplay.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.ComponentLike; 5 | import net.kyori.adventure.text.format.NamedTextColor; 6 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam; 7 | import net.megavex.scoreboardlibrary.api.team.TeamDisplay; 8 | import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule; 9 | import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | import static net.kyori.adventure.text.Component.empty; 19 | 20 | class NoopTeamDisplay implements TeamDisplay { 21 | private final NoopScoreboardTeam team; 22 | 23 | private final Set entries = new HashSet<>(); 24 | private Component displayName = empty(), 25 | prefix = empty(), 26 | suffix = empty(); 27 | private boolean friendlyFire, canSeeFriendlyInvisibles; 28 | private NameTagVisibility nameTagVisibility = NameTagVisibility.ALWAYS; 29 | private CollisionRule collisionRule = CollisionRule.ALWAYS; 30 | private NamedTextColor playerColor = null; 31 | 32 | NoopTeamDisplay(@NotNull NoopScoreboardTeam team) { 33 | this.team = team; 34 | } 35 | 36 | @Override 37 | public @NotNull ScoreboardTeam team() { 38 | return team; 39 | } 40 | 41 | @Override 42 | public @NotNull Collection entries() { 43 | return Collections.unmodifiableSet(entries); 44 | } 45 | 46 | @Override 47 | public boolean addEntry(@NotNull String entry) { 48 | return entries.add(entry); 49 | } 50 | 51 | @Override 52 | public boolean removeEntry(@NotNull String entry) { 53 | return entries.remove(entry); 54 | } 55 | 56 | @Override 57 | public @NotNull Component displayName() { 58 | return displayName; 59 | } 60 | 61 | @Override 62 | public @NotNull TeamDisplay displayName(@NotNull ComponentLike displayName) { 63 | this.displayName = displayName.asComponent(); 64 | return this; 65 | } 66 | 67 | @Override 68 | public @NotNull Component prefix() { 69 | return prefix; 70 | } 71 | 72 | @Override 73 | public @NotNull TeamDisplay prefix(@NotNull ComponentLike prefix) { 74 | this.prefix = prefix.asComponent(); 75 | return this; 76 | } 77 | 78 | @Override 79 | public @NotNull Component suffix() { 80 | return suffix; 81 | } 82 | 83 | @Override 84 | public @NotNull TeamDisplay suffix(@NotNull ComponentLike suffix) { 85 | this.suffix = suffix.asComponent(); 86 | return this; 87 | } 88 | 89 | @Override 90 | public boolean friendlyFire() { 91 | return friendlyFire; 92 | } 93 | 94 | @Override 95 | public @NotNull TeamDisplay friendlyFire(boolean friendlyFire) { 96 | this.friendlyFire = friendlyFire; 97 | return this; 98 | } 99 | 100 | @Override 101 | public boolean canSeeFriendlyInvisibles() { 102 | return canSeeFriendlyInvisibles; 103 | } 104 | 105 | @Override 106 | public @NotNull TeamDisplay canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) { 107 | this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles; 108 | return this; 109 | } 110 | 111 | @Override 112 | public @NotNull NameTagVisibility nameTagVisibility() { 113 | return nameTagVisibility; 114 | } 115 | 116 | @Override 117 | public @NotNull TeamDisplay nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility) { 118 | this.nameTagVisibility = nameTagVisibility; 119 | return this; 120 | } 121 | 122 | @Override 123 | public @NotNull CollisionRule collisionRule() { 124 | return collisionRule; 125 | } 126 | 127 | @Override 128 | public @NotNull TeamDisplay collisionRule(@NotNull CollisionRule collisionRule) { 129 | this.collisionRule = collisionRule; 130 | return this; 131 | } 132 | 133 | @Override 134 | public @Nullable NamedTextColor playerColor() { 135 | return playerColor; 136 | } 137 | 138 | @Override 139 | public @NotNull TeamDisplay playerColor(@Nullable NamedTextColor playerColor) { 140 | this.playerColor = playerColor; 141 | return this; 142 | } 143 | 144 | @Override 145 | public void refresh() { 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Latest version: `2.4.4` 4 | 5 | ## Gradle 6 | 7 | ```kotlin 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | val scoreboardLibraryVersion = "{VERSION HERE}" 14 | implementation("net.megavex:scoreboard-library-api:$scoreboardLibraryVersion") 15 | runtimeOnly("net.megavex:scoreboard-library-implementation:$scoreboardLibraryVersion") 16 | implementation("net.megavex:scoreboard-library-extra-kotlin:$scoreboardLibraryVersion") // Kotlin specific extensions (optional) 17 | 18 | // Add packet adapter implementations you want: 19 | runtimeOnly("net.megavex:scoreboard-library-modern:$scoreboardLibraryVersion") // 1.17+ 20 | runtimeOnly("net.megavex:scoreboard-library-modern:$scoreboardLibraryVersion:mojmap") // Mojang mapped variant (only use if you know what you're doing!) 21 | runtimeOnly("net.megavex:scoreboard-library-packetevents:$scoreboardLibraryVersion") // 1.8+ 22 | runtimeOnly("net.megavex:scoreboard-library-legacy:$scoreboardLibraryVersion") // 1.7.10-1.12.2 23 | 24 | // If using the PacketEvents implementation, scoreboard-library expects PacketEvents to be in the classpath. 25 | // Follow either of: 26 | // - https://github.com/retrooper/packetevents/wiki/Depending-on-pre%E2%80%90built-PacketEvents 27 | // - https://github.com/retrooper/packetevents/wiki/Shading-PacketEvents 28 | // Example how to load PacketEvents in your plugin: 29 | // https://github.com/retrooper/packetevents-example/blob/24f0c842d47362aef122b794dea29b8fee113fa3/thread-safe-listener/src/main/java/main/Main.java 30 | 31 | // If targeting a Minecraft version without native Adventure support, add it as well: 32 | implementation("net.kyori:adventure-platform-bukkit:4.3.4") 33 | } 34 | ``` 35 | 36 | You will need to shade these dependencies and relocate them with something 37 | like [Shadow](https://gradleup.com/shadow/). 38 | 39 | ## Maven 40 | 41 | ```xml 42 | 43 | 44 | net.megavex 45 | scoreboard-library-api 46 | {VERSION HERE} 47 | 48 | 49 | net.megavex 50 | scoreboard-library-implementation 51 | {VERSION HERE} 52 | runtime 53 | 54 | 55 | 56 | net.megavex 57 | scoreboard-library-extra-kotlin 58 | {VERSION HERE} 59 | 60 | 61 | 62 | 63 | net.megavex 64 | scoreboard-library-modern 65 | {VERSION HERE} 66 | runtime 67 | 68 | 69 | 70 | 71 | net.megavex 72 | scoreboard-library-packetevents 73 | {VERSION HERE} 74 | runtime 75 | 76 | 77 | net.megavex 78 | scoreboard-library-legacy 79 | {VERSION HERE} 80 | runtime 81 | 82 | 83 | 91 | 92 | 93 | 94 | net.kyori 95 | adventure-platform-bukkit 96 | 4.0.1 97 | 98 | 99 | ``` 100 | 101 | You will need to shade these dependencies and relocate them with [maven-shade-plugin](https://maven.apache.org/plugins/maven-shade-plugin/). 102 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/team/PaperTeamsPacketAdapterImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.kyori.adventure.text.Component; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.NativeAdventureUtil; 11 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants; 12 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 13 | import net.minecraft.network.protocol.Packet; 14 | import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; 15 | import org.bukkit.entity.Player; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.UnknownNullability; 18 | 19 | import java.util.Collection; 20 | import java.util.Locale; 21 | 22 | public class PaperTeamsPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl { 23 | public PaperTeamsPacketAdapterImpl(@NotNull PacketSender> sender, @NotNull ComponentProvider componentProvider, @NotNull String teamName) { 24 | super(sender, componentProvider, teamName); 25 | } 26 | 27 | @Override 28 | public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties properties) { 29 | return new TeamDisplayPacketAdapterImpl(properties); 30 | } 31 | 32 | private class TeamDisplayPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl.TeamDisplayPacketAdapterImpl { 33 | private final ClientboundSetPlayerTeamPacket.Parameters parameters = PacketAccessors.PARAMETERS_CONSTRUCTOR.invoke(); 34 | private ClientboundSetPlayerTeamPacket createPacket = null; 35 | private ClientboundSetPlayerTeamPacket updatePacket = null; 36 | private Component displayName, prefix, suffix; 37 | 38 | public TeamDisplayPacketAdapterImpl(@NotNull ImmutableTeamProperties properties) { 39 | super(properties); 40 | } 41 | 42 | @Override 43 | public void updateTeamPackets() { 44 | fillParameters(parameters, null); 45 | createPacket = null; 46 | updatePacket = null; 47 | } 48 | 49 | @Override 50 | public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection players) { 51 | if (createPacket == null || updatePacket == null) { 52 | Collection entries = ImmutableList.copyOf(properties.syncedEntries()); 53 | createPacket = createTeamsPacket(TeamConstants.MODE_CREATE, teamName, parameters, entries); 54 | updatePacket = createTeamsPacket(TeamConstants.MODE_UPDATE, teamName, parameters, entries); 55 | } 56 | 57 | switch (packetType) { 58 | case CREATE: 59 | sender.sendPacket(players, createPacket); 60 | break; 61 | case UPDATE: 62 | sender.sendPacket(players, updatePacket); 63 | break; 64 | } 65 | } 66 | 67 | @Override 68 | protected void fillParameters(ClientboundSetPlayerTeamPacket.@NotNull Parameters parameters, @UnknownNullability Locale locale) { 69 | super.fillParameters(parameters, locale); 70 | 71 | if (properties.displayName() != displayName) { 72 | net.minecraft.network.chat.Component vanilla = NativeAdventureUtil.fromAdventureComponent(properties.displayName()); 73 | PacketAccessors.DISPLAY_NAME_FIELD.set(parameters, vanilla); 74 | displayName = properties.displayName(); 75 | } 76 | 77 | if (properties.prefix() != prefix) { 78 | net.minecraft.network.chat.Component vanilla = NativeAdventureUtil.fromAdventureComponent(properties.prefix()); 79 | PacketAccessors.PREFIX_FIELD.set(parameters, vanilla); 80 | prefix = properties.prefix(); 81 | } 82 | 83 | if (properties.suffix() != suffix) { 84 | net.minecraft.network.chat.Component vanilla = NativeAdventureUtil.fromAdventureComponent(properties.suffix()); 85 | PacketAccessors.SUFFIX_FIELD.set(parameters, vanilla); 86 | suffix = properties.suffix(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /api/src/main/java/net/megavex/scoreboardlibrary/api/noop/NoopTeamManager.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.api.noop; 2 | 3 | import com.google.common.base.Preconditions; 4 | import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam; 5 | import net.megavex.scoreboardlibrary.api.team.TeamDisplay; 6 | import net.megavex.scoreboardlibrary.api.team.TeamManager; 7 | import org.bukkit.entity.Player; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.*; 12 | import java.util.function.BiFunction; 13 | import java.util.function.Function; 14 | 15 | class NoopTeamManager implements TeamManager { 16 | private final Set players = new HashSet<>(); 17 | private final Map teams = new HashMap<>(); 18 | private boolean closed; 19 | 20 | @Override 21 | public void close() { 22 | closed = true; 23 | } 24 | 25 | @Override 26 | public boolean closed() { 27 | return closed; 28 | } 29 | 30 | @Override 31 | public @NotNull Collection players() { 32 | return closed ? Collections.emptySet() : Collections.unmodifiableSet(players); 33 | } 34 | 35 | @Override 36 | public boolean removePlayer(@NotNull Player player) { 37 | Preconditions.checkNotNull(player); 38 | checkClosed(); 39 | 40 | if (!players.remove(player)) { 41 | return false; 42 | } 43 | 44 | for (NoopScoreboardTeam team : teams.values()) { 45 | team.displayMap().remove(player); 46 | } 47 | 48 | return true; 49 | } 50 | 51 | @Override 52 | public @NotNull Collection teams() { 53 | return closed ? Collections.emptySet() : Collections.unmodifiableCollection(teams.values()); 54 | } 55 | 56 | @Override 57 | public @Nullable ScoreboardTeam team(@NotNull String name) { 58 | return teams.get(name.toLowerCase()); 59 | } 60 | 61 | @Override 62 | public boolean teamExists(@NotNull String name) { 63 | return teams.containsKey(name.toLowerCase()); 64 | } 65 | 66 | @Override 67 | public @NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction teamDisplayFunction) { 68 | Preconditions.checkNotNull(name); 69 | checkClosed(); 70 | 71 | name = name.toLowerCase(); 72 | NoopScoreboardTeam team = teams.get(name); 73 | if (team != null) { 74 | return team; 75 | } 76 | 77 | team = new NoopScoreboardTeam(this, name); 78 | for (Player player : players) { 79 | TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(player, team); 80 | validateTeamDisplay(team, teamDisplay); 81 | team.displayMap().put(player, teamDisplay); 82 | } 83 | 84 | return team; 85 | } 86 | 87 | @Override 88 | public boolean removeTeam(@NotNull String name) { 89 | Preconditions.checkNotNull(name); 90 | checkClosed(); 91 | 92 | return teams.remove(name.toLowerCase()) != null; 93 | } 94 | 95 | @Override 96 | public void removeTeam(@NotNull ScoreboardTeam team) { 97 | Preconditions.checkNotNull(team); 98 | Preconditions.checkArgument(team.teamManager() == this); 99 | checkClosed(); 100 | 101 | teams.remove(team.name(), (NoopScoreboardTeam) team); 102 | } 103 | 104 | @Override 105 | public boolean addPlayer(@NotNull Player player, @Nullable Function teamDisplayFunction) { 106 | Preconditions.checkNotNull(player); 107 | checkClosed(); 108 | 109 | if (!players.add(player)) { 110 | return false; 111 | } 112 | 113 | for (NoopScoreboardTeam team : teams.values()) { 114 | TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(team); 115 | validateTeamDisplay(team, teamDisplay); 116 | team.displayMap().put(player, teamDisplay); 117 | } 118 | 119 | return true; 120 | } 121 | 122 | private void validateTeamDisplay(@NotNull ScoreboardTeam team, @Nullable TeamDisplay teamDisplay) { 123 | if (teamDisplay == null || teamDisplay.team() != team) { 124 | throw new IllegalArgumentException("invalid TeamDisplay"); 125 | } 126 | 127 | if (!(teamDisplay instanceof NoopTeamDisplay)) { 128 | throw new IllegalArgumentException("must be TeamDisplayImpl"); 129 | } 130 | } 131 | 132 | private void checkClosed() { 133 | if (closed) { 134 | throw new IllegalStateException("NoopTeamManager is closed"); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /versions/modern/src/main/java/net/megavex/scoreboardlibrary/implementation/packetAdapter/modern/team/AbstractTeamsPacketAdapterImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil; 5 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties; 6 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.ComponentProvider; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors; 9 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType; 10 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants; 11 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter; 12 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter; 13 | import net.minecraft.ChatFormatting; 14 | import net.minecraft.network.protocol.Packet; 15 | import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; 16 | import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket.Parameters; 17 | import net.minecraft.world.scores.Team; 18 | import org.bukkit.entity.Player; 19 | import org.jetbrains.annotations.NotNull; 20 | import org.jetbrains.annotations.Nullable; 21 | import org.jetbrains.annotations.UnknownNullability; 22 | 23 | import java.util.*; 24 | 25 | public abstract class AbstractTeamsPacketAdapterImpl implements TeamsPacketAdapter { 26 | protected final PacketSender> sender; 27 | protected final ComponentProvider componentProvider; 28 | protected final String teamName; 29 | private ClientboundSetPlayerTeamPacket removePacket; 30 | 31 | public AbstractTeamsPacketAdapterImpl(@NotNull PacketSender> sender, @NotNull ComponentProvider componentProvider, @NotNull String teamName) { 32 | this.sender = sender; 33 | this.componentProvider = componentProvider; 34 | this.teamName = teamName; 35 | } 36 | 37 | public static ClientboundSetPlayerTeamPacket createTeamsPacket( 38 | int method, 39 | @NotNull String name, 40 | @Nullable Parameters parameters, 41 | @Nullable Collection entries 42 | ) { 43 | return PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke( 44 | name, 45 | method, 46 | Optional.ofNullable(parameters), 47 | entries == null ? Collections.emptyList() : entries 48 | ); 49 | } 50 | 51 | @Override 52 | public void removeTeam(@NotNull Iterable players) { 53 | if (removePacket == null) { 54 | removePacket = createTeamsPacket(TeamConstants.MODE_REMOVE, teamName, null, null); 55 | } 56 | sender.sendPacket(players, removePacket); 57 | } 58 | 59 | public abstract class TeamDisplayPacketAdapterImpl implements TeamDisplayPacketAdapter { 60 | protected final ImmutableTeamProperties properties; 61 | 62 | public TeamDisplayPacketAdapterImpl(ImmutableTeamProperties properties) { 63 | this.properties = properties; 64 | } 65 | 66 | @Override 67 | public void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection players, @NotNull Collection entries) { 68 | sender.sendPacket(players, createTeamsPacket(TeamConstants.mode(packetType), teamName, null, entries)); 69 | } 70 | 71 | protected void fillParameters(@NotNull Parameters parameters, @UnknownNullability Locale locale) { 72 | if (PacketAccessors.IS_1_21_5_OR_ABOVE) { 73 | Objects.requireNonNull(PacketAccessors.NAME_TAG_VISIBILITY_FIELD_1_21_5) 74 | .set(parameters, Team.Visibility.valueOf(properties.nameTagVisibility().name())); 75 | 76 | Objects.requireNonNull(PacketAccessors.COLLISION_RULE_FIELD_1_21_5) 77 | .set(parameters, Team.CollisionRule.valueOf(properties.collisionRule().name())); 78 | } else { 79 | Objects.requireNonNull(PacketAccessors.NAME_TAG_VISIBILITY_FIELD_1_21_4) 80 | .set(parameters, properties.nameTagVisibility().key()); 81 | 82 | Objects.requireNonNull(PacketAccessors.COLLISION_RULE_FIELD_1_21_4) 83 | .set(parameters, properties.collisionRule().key()); 84 | } 85 | 86 | char legacyChar = LegacyFormatUtil.getChar(properties.playerColor()); 87 | PacketAccessors.COLOR_FIELD.set(parameters, ChatFormatting.getByCode(legacyChar)); 88 | 89 | int options = properties.packOptions(); 90 | PacketAccessors.OPTIONS_FIELD.set(parameters, options); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/PacketAdapterLoader.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation; 2 | 3 | import net.megavex.scoreboardlibrary.api.exception.NoPacketAdapterAvailableException; 4 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider; 5 | import org.bukkit.Bukkit; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | 11 | public final class PacketAdapterLoader { 12 | private static final String MODERN = "modern", 13 | LEGACY = "legacy", 14 | PACKET_EVENTS = "packetevents"; 15 | 16 | private PacketAdapterLoader() { 17 | } 18 | 19 | public static @NotNull PacketAdapterProvider loadPacketAdapter() throws NoPacketAdapterAvailableException { 20 | Class nmsClass = findAndLoadImplementationClass(); 21 | if (nmsClass == null) { 22 | throw new NoPacketAdapterAvailableException(); 23 | } 24 | 25 | try { 26 | return (PacketAdapterProvider) nmsClass.getConstructors()[0].newInstance(); 27 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 28 | throw new RuntimeException("couldn't initialize packet adapter", e); 29 | } 30 | } 31 | 32 | private static @Nullable Class findAndLoadImplementationClass() { 33 | String version = Bukkit.getServer().getBukkitVersion(); 34 | int dashIndex = version.indexOf('-'); 35 | if (dashIndex != -1) { 36 | version = version.substring(0, dashIndex); 37 | } 38 | 39 | Class nmsClass = tryLoadVersion(version); 40 | if (nmsClass != null) { 41 | return nmsClass; 42 | } 43 | 44 | return tryLoadPacketEvents(); 45 | } 46 | 47 | private static @Nullable Class tryLoadVersion(@NotNull String serverVersion) { 48 | // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy/ 49 | // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-10-1-15/ 50 | // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16/ 51 | // https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-21/ 52 | switch (serverVersion) { 53 | case "1.7.10": 54 | case "1.8": 55 | case "1.8.3": 56 | case "1.8.4": 57 | case "1.8.5": 58 | case "1.8.6": 59 | case "1.8.7": 60 | case "1.8.8": 61 | case "1.9": 62 | case "1.9.2": 63 | case "1.9.4": 64 | case "1.10.2": 65 | case "1.11": 66 | case "1.11.2": 67 | case "1.12": 68 | case "1.12.1": 69 | case "1.12.2": 70 | return tryLoadImplementationClass(LEGACY); 71 | case "1.17": 72 | case "1.17.1": 73 | case "1.18": 74 | case "1.18.1": 75 | case "1.18.2": 76 | case "1.19": 77 | case "1.19.1": 78 | case "1.19.2": 79 | case "1.19.3": 80 | case "1.19.4": 81 | case "1.20": 82 | case "1.20.1": 83 | case "1.20.2": 84 | case "1.20.3": 85 | case "1.20.4": 86 | case "1.20.5": 87 | case "1.20.6": 88 | case "1.21": 89 | case "1.21.1": 90 | case "1.21.2": 91 | case "1.21.3": 92 | case "1.21.4": 93 | case "1.21.5": 94 | case "1.21.6": 95 | case "1.21.7": 96 | case "1.21.8": 97 | case "1.21.9": 98 | case "1.21.10": 99 | case "1.21.11": 100 | return tryLoadImplementationClass(MODERN); 101 | default: 102 | // Hide from relocation checkers 103 | String property = "net.mega".concat("vex.scoreboardlibrary.forceModern"); 104 | if (System.getProperty(property, "").equalsIgnoreCase("true")) { 105 | return tryLoadImplementationClass(MODERN); 106 | } 107 | 108 | return null; 109 | } 110 | } 111 | 112 | private static @Nullable Class tryLoadPacketEvents() { 113 | Class nmsClass = tryLoadImplementationClass(PACKET_EVENTS); 114 | if (nmsClass == null) { 115 | return null; 116 | } 117 | 118 | try { 119 | Class.forName("com.github.retrooper.packetevents.PacketEvents"); 120 | return nmsClass; 121 | } catch (ClassNotFoundException ignored) { 122 | return null; 123 | } 124 | } 125 | 126 | private static @Nullable Class tryLoadImplementationClass(@NotNull String name) { 127 | try { 128 | String path = "net.megavex.scoreboardlibrary.implementation.packetAdapter." + name + ".PacketAdapterProviderImpl"; 129 | return Class.forName(path); 130 | } catch (ClassNotFoundException ignored) { 131 | return null; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/team/TeamManagerTask.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.team; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Collection; 7 | 8 | public class TeamManagerTask { 9 | private TeamManagerTask() { 10 | } 11 | 12 | public static final class Close extends TeamManagerTask { 13 | public static final Close INSTANCE = new Close(); 14 | 15 | private Close() { 16 | } 17 | } 18 | 19 | public static final class AddPlayer extends TeamManagerTask { 20 | private final Player player; 21 | 22 | public AddPlayer(@NotNull Player player) { 23 | this.player = player; 24 | } 25 | 26 | public @NotNull Player player() { 27 | return player; 28 | } 29 | } 30 | 31 | public static final class RemovePlayer extends TeamManagerTask { 32 | private final Player player; 33 | 34 | public RemovePlayer(@NotNull Player player) { 35 | this.player = player; 36 | } 37 | 38 | public @NotNull Player player() { 39 | return player; 40 | } 41 | } 42 | 43 | public static final class ReloadPlayer extends TeamManagerTask { 44 | private final Player player; 45 | 46 | public ReloadPlayer(@NotNull Player player) { 47 | this.player = player; 48 | } 49 | 50 | public @NotNull Player player() { 51 | return player; 52 | } 53 | } 54 | 55 | public static final class AddTeam extends TeamManagerTask { 56 | private final ScoreboardTeamImpl team; 57 | 58 | public AddTeam(@NotNull ScoreboardTeamImpl team) { 59 | this.team = team; 60 | } 61 | 62 | public @NotNull ScoreboardTeamImpl team() { 63 | return team; 64 | } 65 | } 66 | 67 | public static final class RemoveTeam extends TeamManagerTask { 68 | private final ScoreboardTeamImpl team; 69 | 70 | public RemoveTeam(@NotNull ScoreboardTeamImpl team) { 71 | this.team = team; 72 | } 73 | 74 | public @NotNull ScoreboardTeamImpl team() { 75 | return team; 76 | } 77 | } 78 | 79 | public static final class UpdateTeamDisplay extends TeamManagerTask { 80 | private final TeamDisplayImpl teamDisplay; 81 | 82 | public UpdateTeamDisplay(@NotNull TeamDisplayImpl teamDisplay) { 83 | this.teamDisplay = teamDisplay; 84 | } 85 | 86 | public @NotNull TeamDisplayImpl teamDisplay() { 87 | return teamDisplay; 88 | } 89 | } 90 | 91 | public static final class AddEntries extends TeamManagerTask { 92 | private final TeamDisplayImpl teamDisplay; 93 | private final Collection entry; 94 | 95 | public AddEntries(@NotNull TeamDisplayImpl teamDisplay, @NotNull Collection entries) { 96 | this.teamDisplay = teamDisplay; 97 | this.entry = entries; 98 | } 99 | 100 | public @NotNull TeamDisplayImpl teamDisplay() { 101 | return teamDisplay; 102 | } 103 | 104 | public @NotNull Collection entries() { 105 | return entry; 106 | } 107 | } 108 | 109 | public static final class RemoveEntries extends TeamManagerTask { 110 | private final TeamDisplayImpl teamDisplay; 111 | private final Collection entry; 112 | 113 | public RemoveEntries(@NotNull TeamDisplayImpl teamDisplay, @NotNull Collection entries) { 114 | this.teamDisplay = teamDisplay; 115 | this.entry = entries; 116 | } 117 | 118 | public @NotNull TeamDisplayImpl teamDisplay() { 119 | return teamDisplay; 120 | } 121 | 122 | public @NotNull Collection entries() { 123 | return entry; 124 | } 125 | } 126 | 127 | public static final class ChangeTeamDisplay extends TeamManagerTask { 128 | private final Player player; 129 | private final ScoreboardTeamImpl team; 130 | private final TeamDisplayImpl oldTeamDisplay; 131 | private final TeamDisplayImpl newTeamDisplay; 132 | 133 | public ChangeTeamDisplay(@NotNull Player player, @NotNull ScoreboardTeamImpl team, @NotNull TeamDisplayImpl oldTeamDisplay, @NotNull TeamDisplayImpl newTeamDisplay) { 134 | this.player = player; 135 | this.team = team; 136 | this.oldTeamDisplay = oldTeamDisplay; 137 | this.newTeamDisplay = newTeamDisplay; 138 | } 139 | 140 | public @NotNull Player player() { 141 | return player; 142 | } 143 | 144 | public @NotNull ScoreboardTeamImpl team() { 145 | return team; 146 | } 147 | 148 | public @NotNull TeamDisplayImpl oldTeamDisplay() { 149 | return oldTeamDisplay; 150 | } 151 | 152 | public @NotNull TeamDisplayImpl newTeamDisplay() { 153 | return newTeamDisplay; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /implementation/src/main/java/net/megavex/scoreboardlibrary/implementation/objective/ScoreboardObjectiveImpl.java: -------------------------------------------------------------------------------- 1 | package net.megavex.scoreboardlibrary.implementation.objective; 2 | 3 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType; 4 | import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore; 5 | import net.megavex.scoreboardlibrary.api.objective.ScoreFormat; 6 | import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective; 7 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType; 8 | import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter; 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.Queue; 14 | import net.kyori.adventure.text.Component; 15 | import net.kyori.adventure.text.ComponentLike; 16 | import org.bukkit.entity.Player; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import static net.kyori.adventure.text.Component.empty; 21 | 22 | public class ScoreboardObjectiveImpl implements ScoreboardObjective { 23 | private final ObjectivePacketAdapter packetAdapter; 24 | private final Queue taskQueue; 25 | private final String name; 26 | 27 | private final Map scores = new HashMap<>(); 28 | private Component value = empty(); 29 | private ObjectiveRenderType renderType = ObjectiveRenderType.INTEGER; 30 | private ScoreFormat defaultScoreFormat; 31 | private boolean closed; 32 | 33 | public ScoreboardObjectiveImpl(@NotNull ObjectivePacketAdapter packetAdapter, @NotNull Queue taskQueue, @NotNull String name) { 34 | this.packetAdapter = packetAdapter; 35 | this.taskQueue = taskQueue; 36 | this.name = name; 37 | } 38 | 39 | public ObjectivePacketAdapter packetAdapter() { 40 | return packetAdapter; 41 | } 42 | 43 | public @NotNull Map scores() { 44 | return scores; 45 | } 46 | 47 | public @NotNull String name() { 48 | return name; 49 | } 50 | 51 | public void close() { 52 | closed = true; 53 | } 54 | 55 | @Override 56 | public @NotNull Component value() { 57 | return value; 58 | } 59 | 60 | @Override 61 | public @NotNull ScoreboardObjective value(@NotNull ComponentLike value) { 62 | Component component = value.asComponent(); 63 | if (!this.value.equals(component)) { 64 | this.value = component; 65 | if (!closed) { 66 | taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this)); 67 | } 68 | } 69 | return this; 70 | } 71 | 72 | @Override 73 | public @NotNull ObjectiveRenderType renderType() { 74 | return renderType; 75 | } 76 | 77 | @Override 78 | public @NotNull ScoreboardObjective renderType(@NotNull ObjectiveRenderType renderType) { 79 | if (this.renderType != renderType) { 80 | this.renderType = renderType; 81 | if (!closed) { 82 | taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this)); 83 | } 84 | } 85 | return this; 86 | } 87 | 88 | @Override 89 | public @Nullable ScoreFormat defaultScoreFormat() { 90 | return defaultScoreFormat; 91 | } 92 | 93 | @Override 94 | public void defaultScoreFormat(@Nullable ScoreFormat defaultScoreFormat) { 95 | if (!Objects.equals(this.defaultScoreFormat, defaultScoreFormat)) { 96 | this.defaultScoreFormat = defaultScoreFormat; 97 | taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this)); 98 | } 99 | } 100 | 101 | @Override 102 | public void refreshProperties() { 103 | if (!closed) { 104 | taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this)); 105 | } 106 | } 107 | 108 | @Override 109 | public @Nullable ObjectiveScore scoreInfo(@NotNull String entry) { 110 | return scores.get(entry); 111 | } 112 | 113 | @Override 114 | public @NotNull ScoreboardObjective score(@NotNull String entry, ObjectiveScore score) { 115 | ObjectiveScore oldScore = scores.put(entry, score); 116 | if (!Objects.equals(oldScore, score)) { 117 | taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, score)); 118 | } 119 | return this; 120 | } 121 | 122 | @Override 123 | public @NotNull ScoreboardObjective removeScore(@NotNull String entry) { 124 | if (scores.remove(entry) != null) { 125 | taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, null)); 126 | } 127 | return this; 128 | } 129 | 130 | @Override 131 | public void refreshScore(@NotNull String entry) { 132 | ObjectiveScore score = scores.get(entry); 133 | if (score != null) { 134 | taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, score)); 135 | } 136 | } 137 | 138 | public void sendProperties(@NotNull Collection players, @NotNull PropertiesPacketType packetType) { 139 | packetAdapter.sendProperties(players, packetType, value, renderType, defaultScoreFormat); 140 | } 141 | } 142 | --------------------------------------------------------------------------------