From 2a9ca30f6462dbef22da38574c258fdaa8cf6344 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 15 Mar 2025 19:04:44 +0100 Subject: [PATCH] Add FancyVisuals plugin --- gradle.properties | 1 + plugins/fancyvisuals/.gitignore | 120 +++++++++++++ plugins/fancyvisuals/README.md | 51 ++++++ plugins/fancyvisuals/api/build.gradle.kts | 57 ++++++ .../de/oliver/fancyvisuals/api/Context.java | 34 ++++ .../fancyvisuals/api/FancyVisualsAPI.java | 11 ++ .../fancyvisuals/api/nametags/Nametag.java | 33 ++++ .../api/nametags/NametagRepository.java | 55 ++++++ .../api/nametags/NametagStore.java | 44 +++++ plugins/fancyvisuals/build.gradle.kts | 131 ++++++++++++++ .../de/oliver/fancyvisuals/FancyVisuals.java | 118 +++++++++++++ .../analytics/AnalyticsManager.java | 44 +++++ .../config/FancyVisualsConfig.java | 19 ++ .../fancyvisuals/config/NametagConfig.java | 23 +++ .../loaders/FancyVisualsBootstrapper.java | 14 ++ .../loaders/FancyVisualsLoader.java | 27 +++ .../nametags/fake/FakeNametagRepository.java | 58 ++++++ .../nametags/fake/FakeNametagStore.java | 39 +++++ .../nametags/listeners/NametagListeners.java | 29 +++ .../nametags/store/JsonNametagRepository.java | 119 +++++++++++++ .../nametags/store/JsonNametagStore.java | 67 +++++++ .../nametags/visibility/PlayerNametag.java | 165 ++++++++++++++++++ .../visibility/PlayerNametagScheduler.java | 57 ++++++ .../playerConfig/JsonPlayerConfigStore.java | 100 +++++++++++ .../playerConfig/PlayerConfig.java | 14 ++ .../fancyvisuals/utils/VaultHelper.java | 30 ++++ .../utils/distributedWorkload/Bucket.java | 64 +++++++ .../DistributedWorkload.java | 100 +++++++++++ settings.gradle.kts | 3 + 29 files changed, 1627 insertions(+) create mode 100644 plugins/fancyvisuals/.gitignore create mode 100644 plugins/fancyvisuals/README.md create mode 100644 plugins/fancyvisuals/api/build.gradle.kts create mode 100644 plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/Context.java create mode 100644 plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/FancyVisualsAPI.java create mode 100644 plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/Nametag.java create mode 100644 plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagRepository.java create mode 100644 plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagStore.java create mode 100644 plugins/fancyvisuals/build.gradle.kts create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/FancyVisuals.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/analytics/AnalyticsManager.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/FancyVisualsConfig.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/NametagConfig.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsBootstrapper.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsLoader.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagRepository.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagStore.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/listeners/NametagListeners.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagRepository.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagStore.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametag.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametagScheduler.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/JsonPlayerConfigStore.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/PlayerConfig.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/VaultHelper.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/Bucket.java create mode 100644 plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/DistributedWorkload.java diff --git a/gradle.properties b/gradle.properties index fcb92eaa..d42fbb62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ fancyhologramsVersion=2.4.2 +fancyvisualsVersion=0.0.1 fancylibVersion=36 fancysitulaVersion=0.0.13 jdbVersion=1.0.0 diff --git a/plugins/fancyvisuals/.gitignore b/plugins/fancyvisuals/.gitignore new file mode 100644 index 00000000..e3bd7a2c --- /dev/null +++ b/plugins/fancyvisuals/.gitignore @@ -0,0 +1,120 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +run/ + +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +run/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/plugins/fancyvisuals/README.md b/plugins/fancyvisuals/README.md new file mode 100644 index 00000000..9561854f --- /dev/null +++ b/plugins/fancyvisuals/README.md @@ -0,0 +1,51 @@ +# FancyVisuals + +**Do not use this plugin in production! It is still in development and may contain bugs and unfinished features.** + +This is a plugin to customise most visual components of your minecraft server. This includes the scoreboard, tablist, +bossbar, actionbar, title, chat and nametags. This plugin is highly customisable and can be used to create a unique +experience for your players. +This plugin is packet based (powered by FancySitula), meaning it is blazing fast and has no impact on server +performance. You can use placeholders by PlaceholderAPI anywhere in the plugin. + +## Features + +The plugin is divided into multiple modules, each of which can be configured individually. + +### Nametags + +- With the nametags module, you can customise the nametags (text above the player's head) of players +- The nametags are implemented using display entities +- The nametags can have multiple lines and a configurable background +- You can use MiniMessage for coloring and formatting +- Placeholders by PlaceholderAPI are supported + +### Scoreboard + +Comming soon + +### Tablist + +Comming soon + +### Bossbar + +Comming soon + +### Actionbar + +Comming soon + +### Title & Subtitle + +Comming soon + +### Chat + +Comming soon + +## Installation + +Paper **1.20.5** - **1.21.4** with **Java 21** (or higher) is required. + +**Spigot** is **not** supported. \ No newline at end of file diff --git a/plugins/fancyvisuals/api/build.gradle.kts b/plugins/fancyvisuals/api/build.gradle.kts new file mode 100644 index 00000000..a1e49d69 --- /dev/null +++ b/plugins/fancyvisuals/api/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + id("java-library") + id("maven-publish") +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") +} + +tasks { + publishing { + repositories { + maven { + name = "fancypluginsReleases" + url = uri("https://repo.fancyplugins.de/releases") + credentials(PasswordCredentials::class) + authentication { + isAllowInsecureProtocol = true + create("basic") + } + } + + maven { + name = "fancypluginsSnapshots" + url = uri("https://repo.fancyplugins.de/snapshots") + credentials(PasswordCredentials::class) + authentication { + isAllowInsecureProtocol = true + create("basic") + } + } + } + publications { + create("maven") { + groupId = "de.oliver" + artifactId = "FancyVisuals" + version = findProperty("fancyvisualsVersion") as String + from(project.components["java"]) + } + } + } + + java { + withSourcesJar() + withJavadocJar() + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + options.release = 21 + + } +} \ No newline at end of file diff --git a/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/Context.java b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/Context.java new file mode 100644 index 00000000..271234dc --- /dev/null +++ b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/Context.java @@ -0,0 +1,34 @@ +package de.oliver.fancyvisuals.api; + +/** + * Enum representing different contexts in which operations can be performed. + * Each context is associated with a specific priority level that indicates its specificity. + */ +public enum Context { + + SERVER(1), + WORLD(2), + GROUP(3), + PLAYER(4), + ; + + private final int priority; + + Context(int priority) { + this.priority = priority; + } + + public String getName() { + return name().toLowerCase(); + } + + /** + * Retrieves the priority level associated with this context. + * Higher priority levels indicate more specific contexts. + * + * @return the priority level as an integer + */ + public int getPriority() { + return priority; + } +} diff --git a/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/FancyVisualsAPI.java b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/FancyVisualsAPI.java new file mode 100644 index 00000000..2e63616d --- /dev/null +++ b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/FancyVisualsAPI.java @@ -0,0 +1,11 @@ +package de.oliver.fancyvisuals.api; + +import de.oliver.fancyvisuals.api.nametags.NametagRepository; +import org.bukkit.plugin.java.JavaPlugin; + +public interface FancyVisualsAPI { + + JavaPlugin getPlugin(); + + NametagRepository getNametagRepository(); +} diff --git a/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/Nametag.java b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/Nametag.java new file mode 100644 index 00000000..51f5a4a9 --- /dev/null +++ b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/Nametag.java @@ -0,0 +1,33 @@ +package de.oliver.fancyvisuals.api.nametags; + +import com.google.gson.annotations.SerializedName; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record Nametag( + @SerializedName("text_lines") + @NotNull List textLines, + + @SerializedName("background_color") + @NotNull String backgroundColor, + + @SerializedName("text_shadow") + @NotNull Boolean textShadow, + + @SerializedName("text_alignment") + @NotNull TextAlignment textAlignment +) { + + public enum TextAlignment { + @SerializedName("left") + LEFT, + + @SerializedName("right") + RIGHT, + + @SerializedName("center") + CENTER + } + +} diff --git a/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagRepository.java b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagRepository.java new file mode 100644 index 00000000..721085f1 --- /dev/null +++ b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagRepository.java @@ -0,0 +1,55 @@ +package de.oliver.fancyvisuals.api.nametags; + +import de.oliver.fancyvisuals.api.Context; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * The {@code NametagRepository} interface provides methods for retrieving {@code NametagStore} and {@code Nametag} + * instances based on different contexts. + */ +public interface NametagRepository { + + /** + * The default {@code Nametag} instance used when no specific nametag is found for a given context or player. + */ + Nametag DEFAULT_NAMETAG = new Nametag( + List.of("%player_name%"), + "#000000", + true, + Nametag.TextAlignment.CENTER + ); + + /** + * Retrieves the {@code NametagStore} associated with the given context. + * + * @param context the context for which the store is to be retrieved. This determines if the store is for SERVER, WORLD, GROUP, or PLAYER. + * @return the NametagStore associated with the provided context. + */ + @NotNull NametagStore getStore(@NotNull Context context); + + /** + * Retrieves the {@code Nametag} associated with the specified {@code id} within the given {@code context}. + * + * @param context the context for which the nametag is to be retrieved. This determines if the nametag is for SERVER, WORLD, GROUP, or PLAYER. + * @param id the unique identifier for the nametag. + * @return the Nametag associated with the given id; may return null if no such nametag is found within the specified context. + */ + default @Nullable Nametag getNametag(@NotNull Context context, @NotNull String id) { + return getStore(context).getNametag(id); + } + + /** + * Retrieves the appropriate {@code Nametag} for the specified {@code Player} based on various contexts. + * The method checks the PLAYER, GROUP, WORLD, and SERVER contexts in order, returning the first matching nametag found. + * If no matching nametag is found in any context, a default nametag is returned. + * + * @param player the Player for whom the nametag is being retrieved + * @return the Nametag associated with the player, or a default nametag if no specific nametag is found + */ + @NotNull Nametag getNametagForPlayer(@NotNull Player player); + +} diff --git a/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagStore.java b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagStore.java new file mode 100644 index 00000000..d9945f06 --- /dev/null +++ b/plugins/fancyvisuals/api/src/main/java/de/oliver/fancyvisuals/api/nametags/NametagStore.java @@ -0,0 +1,44 @@ +package de.oliver.fancyvisuals.api.nametags; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * The {@code NametagStore} interface defines operations for storing, retrieving, and managing {@code Nametag} objects + * associated with unique identifiers. + */ +public interface NametagStore { + + /** + * Associates the specified {@code Nametag} with the provided {@code id} in the store. + * + * @param id the unique identifier for the nametag. + * @param nametag the Nametag object to be associated with the specified id. + */ + void setNametag(@NotNull String id, @NotNull Nametag nametag); + + /** + * Retrieves the {@code Nametag} associated with the specified {@code id}. + * + * @param id the unique identifier for the nametag. + * @return the Nametag associated with the given id; may return null if no such nametag is found. + */ + @Nullable Nametag getNametag(@NotNull String id); + + /** + * Removes the {@code Nametag} associated with the specified {@code id} from the store. + * + * @param id the unique identifier for the nametag to be removed. + */ + void removeNametag(@NotNull String id); + + /** + * Retrieves a list of all the Nametags in the store. + * + * @return a list of Nametag objects; the list may be empty if no nametags are present in the store. + */ + @NotNull List getNametags(); + +} diff --git a/plugins/fancyvisuals/build.gradle.kts b/plugins/fancyvisuals/build.gradle.kts new file mode 100644 index 00000000..38de10e4 --- /dev/null +++ b/plugins/fancyvisuals/build.gradle.kts @@ -0,0 +1,131 @@ +import net.minecrell.pluginyml.paper.PaperPluginDescription + +plugins { + id("java-library") + + id("xyz.jpenilla.run-paper") + id("com.gradleup.shadow") + id("net.minecrell.plugin-yml.paper") +} + +runPaper.folia.registerTask() + +allprojects { + group = "de.oliver" + version = findProperty("fancyvisualsVersion") as String + description = "Simple, lightweight and fast visual plugin using packets" + + repositories { + mavenLocal() + mavenCentral() + maven(url = "https://repo.papermc.io/repository/maven-public/") + maven(url = "https://repo.fancyplugins.de/releases") + maven(url = "https://repo.lushplugins.org/releases") + maven(url = "https://jitpack.io") + } +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyvisuals:api")) + + compileOnly("de.oliver:FancyLib:33") // loaded in FancyVisualLoader + compileOnly("de.oliver:FancySitula:0.0.13") // loaded in FancyVisualLoader + compileOnly("de.oliver.FancyAnalytics:api:0.0.8") // loaded in FancyVisualLoader + compileOnly("de.oliver.FancyAnalytics:logger:0.0.5") // loaded in FancyVisualLoader + + implementation("org.lushplugins:ChatColorHandler:4.0.0") + compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") + + // commands + implementation("org.incendo:cloud-core:2.0.0") + implementation("org.incendo:cloud-paper:2.0.0-beta.10") + implementation("org.incendo:cloud-annotations:2.0.0") + annotationProcessor("org.incendo:cloud-annotations:2.0.0") +} + +paper { + main = "de.oliver.fancyvisuals.FancyVisuals" + bootstrapper = "de.oliver.fancyvisuals.loaders.FancyVisualsBootstrapper" + loader = "de.oliver.fancyvisuals.loaders.FancyVisualsLoader" + foliaSupported = true + version = findProperty("fancyvisualsVersion") as String + description = "Simple, lightweight and fast visuals plugin using packets" + apiVersion = "1.19" + serverDependencies { + register("PlaceholderAPI") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("MiniPlaceholders") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("LuckPerms") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("PermissionsEx") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + } +} + +tasks { + runServer { + minecraftVersion("1.21.4") + + downloadPlugins { + hangar("ViaVersion", "5.0.3") + hangar("ViaBackwards", "5.0.3") + hangar("PlaceholderAPI", "2.11.6") +// modrinth("multiverse-core", "4.3.11") + } + } + + shadowJar { + archiveClassifier.set("") + dependsOn(":plugins:fancyvisuals:api:jar") + } + + compileJava { + options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything + options.release = 21 + // For cloud-annotations, see https://cloud.incendo.org/annotations/#command-components + options.compilerArgs.add("-parameters") + } + + javadoc { + options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything + } + + processResources { + filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything + + val props = mapOf( + "description" to project.description, + "version" to project.version, + "hash" to getCurrentCommitHash(), + ) + + inputs.properties(props) + + filesMatching("paper-plugin.yml") { + expand(props) + } + + filesMatching("version.yml") { + expand(props) + } + } +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +fun getCurrentCommitHash(): String { + return "" +} \ No newline at end of file diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/FancyVisuals.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/FancyVisuals.java new file mode 100644 index 00000000..0c96a811 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/FancyVisuals.java @@ -0,0 +1,118 @@ +package de.oliver.fancyvisuals; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import de.oliver.fancyanalytics.logger.ExtendedFancyLogger; +import de.oliver.fancyanalytics.logger.LogLevel; +import de.oliver.fancylib.FancyLib; +import de.oliver.fancysitula.api.IFancySitula; +import de.oliver.fancyvisuals.analytics.AnalyticsManager; +import de.oliver.fancyvisuals.api.FancyVisualsAPI; +import de.oliver.fancyvisuals.api.nametags.NametagRepository; +import de.oliver.fancyvisuals.config.FancyVisualsConfig; +import de.oliver.fancyvisuals.config.NametagConfig; +import de.oliver.fancyvisuals.nametags.listeners.NametagListeners; +import de.oliver.fancyvisuals.nametags.store.JsonNametagRepository; +import de.oliver.fancyvisuals.nametags.visibility.PlayerNametagScheduler; +import de.oliver.fancyvisuals.playerConfig.JsonPlayerConfigStore; +import de.oliver.fancyvisuals.utils.VaultHelper; +import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public final class FancyVisuals extends JavaPlugin implements FancyVisualsAPI { + + private static final ExtendedFancyLogger logger = IFancySitula.LOGGER; + private static FancyVisuals instance; + private final AnalyticsManager analyticsManager; + private final FancyVisualsConfig fancyVisualsConfig; + private final NametagConfig nametagConfig; + private ExecutorService workerExecutor; + + private JsonPlayerConfigStore playerConfigStore; + + private NametagRepository nametagRepository; + private PlayerNametagScheduler nametagScheduler; + + public FancyVisuals() { + instance = this; + this.analyticsManager = new AnalyticsManager("34c5a33d-0ff0-48b1-8b1c-53620a690c6e", "981ce185-c961-4618-bf61-71a8ed6c3962", "SxIBSDA2MDVkMGUwOTk3MzQ3NjCmP0UU"); + this.fancyVisualsConfig = new FancyVisualsConfig(); + this.nametagConfig = new NametagConfig(); + } + + public static FancyVisuals get() { + return instance; + } + + public static @NotNull ExtendedFancyLogger getFancyLogger() { + return logger; + } + + @Override + public void onLoad() { + FancyLib fancyLib = new FancyLib(this); + IFancySitula.LOGGER.setCurrentLevel(LogLevel.DEBUG); + + // config + fancyVisualsConfig.load(); + nametagConfig.load(); + + // worker executor + this.workerExecutor = Executors.newFixedThreadPool( + fancyVisualsConfig.getAmountWorkerThreads(), + new ThreadFactoryBuilder() + .setNameFormat("FancyVisualsWorker-%d") + .build() + ); + + + // Player config + playerConfigStore = new JsonPlayerConfigStore(); + + // Nametags + nametagRepository = new JsonNametagRepository(); + nametagScheduler = new PlayerNametagScheduler(workerExecutor, nametagConfig.getDistributionBucketSize()); + + // analytics + analyticsManager.init(); + } + + @Override + public void onEnable() { + PluginManager pluginManager = Bukkit.getPluginManager(); + + // Vault + VaultHelper.loadVault(); + + // Nametags + nametagScheduler.init(); + pluginManager.registerEvents(new NametagListeners(), this); + } + + @Override + public void onDisable() { + + } + + @Override + public JavaPlugin getPlugin() { + return instance; + } + + public JsonPlayerConfigStore getPlayerConfigStore() { + return playerConfigStore; + } + + @Override + public NametagRepository getNametagRepository() { + return nametagRepository; + } + + public PlayerNametagScheduler getNametagScheduler() { + return nametagScheduler; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/analytics/AnalyticsManager.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/analytics/AnalyticsManager.java new file mode 100644 index 00000000..8e783446 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/analytics/AnalyticsManager.java @@ -0,0 +1,44 @@ +package de.oliver.fancyvisuals.analytics; + +import de.oliver.fancyanalytics.api.FancyAnalyticsAPI; +import de.oliver.fancyanalytics.api.MetricSupplier; +import de.oliver.fancyvisuals.FancyVisuals; +import de.oliver.fancyvisuals.api.Context; +import de.oliver.fancyvisuals.api.nametags.NametagRepository; + +import java.util.logging.Logger; + +public class AnalyticsManager { + + private final FancyAnalyticsAPI fa; + + public AnalyticsManager(String userId, String projectId, String apiKey) { + this.fa = new FancyAnalyticsAPI(userId, projectId, apiKey); + } + + public void init() { + fa.registerDefaultPluginMetrics(FancyVisuals.get()); + fa.registerLogger(FancyVisuals.get().getLogger()); + fa.registerLogger(Logger.getGlobal()); + + registerNametagMetrics(); + + fa.initialize(); + } + + private void registerNametagMetrics() { + NametagRepository repo = FancyVisuals.get().getNametagRepository(); + + fa.registerNumberMetric(new MetricSupplier<>("nametag_count_total", () -> { + double count = 0; + for (Context ctx : Context.values()) { + count += repo.getStore(ctx).getNametags().size(); + } + return count; + })); + fa.registerNumberMetric(new MetricSupplier<>("nametag_count_player", () -> (double) repo.getStore(Context.PLAYER).getNametags().size())); + fa.registerNumberMetric(new MetricSupplier<>("nametag_count_group", () -> (double) repo.getStore(Context.GROUP).getNametags().size())); + fa.registerNumberMetric(new MetricSupplier<>("nametag_count_world", () -> (double) repo.getStore(Context.WORLD).getNametags().size())); + fa.registerNumberMetric(new MetricSupplier<>("nametag_count_server", () -> (double) repo.getStore(Context.SERVER).getNametags().size())); + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/FancyVisualsConfig.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/FancyVisualsConfig.java new file mode 100644 index 00000000..b8535491 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/FancyVisualsConfig.java @@ -0,0 +1,19 @@ +package de.oliver.fancyvisuals.config; + +public class FancyVisualsConfig { + + private int amountWorkerThreads; + + public FancyVisualsConfig() { + this.amountWorkerThreads = 4; + } + + public void load() { + + } + + public int getAmountWorkerThreads() { + return amountWorkerThreads; + } + +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/NametagConfig.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/NametagConfig.java new file mode 100644 index 00000000..fbda4c66 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/config/NametagConfig.java @@ -0,0 +1,23 @@ +package de.oliver.fancyvisuals.config; + +public class NametagConfig { + + private int distributionBucketSize; + + public NametagConfig() { + distributionBucketSize = 10; + } + + public void load() { + + } + + /** + * Retrieves the size of the distribution bucket configured. + * + * @return The size of the distribution bucket. + */ + public int getDistributionBucketSize() { + return distributionBucketSize; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsBootstrapper.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsBootstrapper.java new file mode 100644 index 00000000..3ae7ba5d --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsBootstrapper.java @@ -0,0 +1,14 @@ +package de.oliver.fancyvisuals.loaders; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import org.jetbrains.annotations.NotNull; + +public class FancyVisualsBootstrapper implements PluginBootstrap { + + @Override + public void bootstrap(@NotNull BootstrapContext context) { + + } + +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsLoader.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsLoader.java new file mode 100644 index 00000000..7522b9fa --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/loaders/FancyVisualsLoader.java @@ -0,0 +1,27 @@ +package de.oliver.fancyvisuals.loaders; + +import io.papermc.paper.plugin.loader.PluginClasspathBuilder; +import io.papermc.paper.plugin.loader.PluginLoader; +import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.jetbrains.annotations.NotNull; + +public class FancyVisualsLoader implements PluginLoader { + + @Override + public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) { + + MavenLibraryResolver resolver = new MavenLibraryResolver(); + resolver.addRepository(new RemoteRepository.Builder("fancyplugins", "default", "https://repo.fancyplugins.de/releases").build()); +// resolver.addRepository(new RemoteRepository.Builder("mavencentral", "default", "https://repo1.maven.org/maven2/").build()); + resolver.addDependency(new Dependency(new DefaultArtifact("de.oliver:FancyLib:33"), "compile")); + resolver.addDependency(new Dependency(new DefaultArtifact("de.oliver:FancySitula:0.0.13"), "compile")); + resolver.addDependency(new Dependency(new DefaultArtifact("de.oliver.FancyAnalytics:api:0.0.5"), "compile")); + resolver.addDependency(new Dependency(new DefaultArtifact("de.oliver.FancyAnalytics:logger:0.0.5"), "compile")); + + classpathBuilder.addLibrary(resolver); + } + +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagRepository.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagRepository.java new file mode 100644 index 00000000..a61c73d5 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagRepository.java @@ -0,0 +1,58 @@ +package de.oliver.fancyvisuals.nametags.fake; + +import de.oliver.fancyvisuals.api.Context; +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.api.nametags.NametagRepository; +import de.oliver.fancyvisuals.api.nametags.NametagStore; +import de.oliver.fancyvisuals.utils.VaultHelper; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class FakeNametagRepository implements NametagRepository { + + private final Map stores; + + public FakeNametagRepository() { + this.stores = new ConcurrentHashMap<>(); + + for (Context ctx : Context.values()) { + stores.put(ctx, new FakeNametagStore()); + } + } + + @Override + public @NotNull NametagStore getStore(@NotNull Context context) { + return stores.get(context); + } + + @Override + @NotNull + public Nametag getNametagForPlayer(@NotNull Player player) { + Nametag nametag = getNametag(Context.PLAYER, player.getUniqueId().toString()); + if (nametag != null) { + return nametag; + } + + if (VaultHelper.isVaultLoaded()) { + nametag = getNametag(Context.GROUP, VaultHelper.getPermission().getPrimaryGroup(player)); + if (nametag != null) { + return nametag; + } + } + + nametag = getNametag(Context.WORLD, player.getWorld().getName()); + if (nametag != null) { + return nametag; + } + + nametag = getNametag(Context.SERVER, "global"); + if (nametag != null) { + return nametag; + } + + return DEFAULT_NAMETAG; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagStore.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagStore.java new file mode 100644 index 00000000..e344ca12 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/fake/FakeNametagStore.java @@ -0,0 +1,39 @@ +package de.oliver.fancyvisuals.nametags.fake; + +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.api.nametags.NametagStore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class FakeNametagStore implements NametagStore { + + private final Map nametags; + + public FakeNametagStore() { + this.nametags = new ConcurrentHashMap<>(); + } + + @Override + public void setNametag(@NotNull String id, @NotNull Nametag nametag) { + nametags.put(id, nametag); + } + + @Override + public @Nullable Nametag getNametag(@NotNull String id) { + return nametags.getOrDefault(id, null); + } + + @Override + public void removeNametag(@NotNull String id) { + nametags.remove(id); + } + + @Override + public @NotNull List getNametags() { + return List.copyOf(nametags.values()); + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/listeners/NametagListeners.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/listeners/NametagListeners.java new file mode 100644 index 00000000..0e3b095f --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/listeners/NametagListeners.java @@ -0,0 +1,29 @@ +package de.oliver.fancyvisuals.nametags.listeners; + +import de.oliver.fancyvisuals.FancyVisuals; +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.nametags.visibility.PlayerNametag; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; + +public class NametagListeners implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Nametag nametag = FancyVisuals.get().getNametagRepository().getNametagForPlayer(player); + PlayerNametag playerNametag = new PlayerNametag(nametag, player); + FancyVisuals.get().getNametagScheduler().add(playerNametag); + } + + @EventHandler + public void onPlayerWorldChange(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + Nametag nametag = FancyVisuals.get().getNametagRepository().getNametagForPlayer(player); + PlayerNametag playerNametag = new PlayerNametag(nametag, player); + FancyVisuals.get().getNametagScheduler().add(playerNametag); + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagRepository.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagRepository.java new file mode 100644 index 00000000..f25e21fe --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagRepository.java @@ -0,0 +1,119 @@ +package de.oliver.fancyvisuals.nametags.store; + +import de.oliver.fancylib.jdb.JDB; +import de.oliver.fancyvisuals.api.Context; +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.api.nametags.NametagRepository; +import de.oliver.fancyvisuals.api.nametags.NametagStore; +import de.oliver.fancyvisuals.utils.VaultHelper; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class JsonNametagRepository implements NametagRepository { + + private static final String BASE_PATH = "plugins/FancyVisuals/data/nametags/"; + private final JDB jdb; + private final Map stores; + + public JsonNametagRepository() { + this.jdb = new JDB(BASE_PATH); + stores = new ConcurrentHashMap<>(); + + for (Context ctx : Context.values()) { + stores.put(ctx, new JsonNametagStore(jdb, ctx)); + } + + initialConfig(); + } + + @Override + public @NotNull NametagStore getStore(@NotNull Context context) { + return stores.get(context); + } + + @Override + @NotNull + public Nametag getNametagForPlayer(@NotNull Player player) { + Nametag nametag = getNametag(Context.PLAYER, player.getUniqueId().toString()); + if (nametag != null) { + return nametag; + } + + if (VaultHelper.isVaultLoaded()) { + nametag = getNametag(Context.GROUP, VaultHelper.getPermission().getPrimaryGroup(player)); + if (nametag != null) { + return nametag; + } + } + + nametag = getNametag(Context.WORLD, player.getWorld().getName()); + if (nametag != null) { + return nametag; + } + + nametag = getNametag(Context.SERVER, "global"); + if (nametag != null) { + return nametag; + } + + return DEFAULT_NAMETAG; + } + + private void initialConfig() { + File baseDir = new File(BASE_PATH); + if (baseDir.exists()) { + return; + } + + NametagStore serverStore = getStore(Context.SERVER); + serverStore.setNametag("global", DEFAULT_NAMETAG); + + NametagStore worldStore = getStore(Context.WORLD); + worldStore.setNametag("world", new Nametag( + List.of("Overworld", "%player%"), + "#C800AA00", + true, + Nametag.TextAlignment.CENTER + )); + worldStore.setNametag("world_nether", new Nametag( + List.of("Nether", "%player%"), + "#C8AA0000", + true, + Nametag.TextAlignment.CENTER + )); + worldStore.setNametag("world_the_end", new Nametag( + List.of("The End", "%player%"), + "#C80000AA", + true, + Nametag.TextAlignment.CENTER + )); + + NametagStore groupStore = getStore(Context.GROUP); + groupStore.setNametag("admin", new Nametag( + List.of("Admin", "%player%"), + "#C8FF0000", + true, + Nametag.TextAlignment.CENTER + )); + groupStore.setNametag("moderator", new Nametag( + List.of("Mod", "%player%"), + "#C8FFAA00", + true, + Nametag.TextAlignment.CENTER + )); + + NametagStore playerStore = getStore(Context.PLAYER); + playerStore.setNametag(UUID.randomUUID().toString(), new Nametag( + List.of("Player", "%player%"), + "#C800FF00", + true, + Nametag.TextAlignment.CENTER + )); + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagStore.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagStore.java new file mode 100644 index 00000000..4eba8a27 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/store/JsonNametagStore.java @@ -0,0 +1,67 @@ +package de.oliver.fancyvisuals.nametags.store; + +import de.oliver.fancylib.jdb.JDB; +import de.oliver.fancyvisuals.FancyVisuals; +import de.oliver.fancyvisuals.api.Context; +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.api.nametags.NametagStore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class JsonNametagStore implements NametagStore { + + private final Context context; + private final JDB jdb; + + public JsonNametagStore(JDB jdb, Context context) { + this.jdb = jdb; + this.context = context; + } + + @Override + public void setNametag(@NotNull String id, @NotNull Nametag nametag) { + try { + jdb.set(context.getName() + "/" + id, nametag); + } catch (IOException e) { + FancyVisuals.getFancyLogger().error("Failed to set nametag for id " + id); + FancyVisuals.getFancyLogger().error(e); + } + } + + @Override + public @Nullable Nametag getNametag(@NotNull String id) { + Nametag nametag = null; + + try { + nametag = jdb.get(context.getName() + "/" + id, Nametag.class); + } catch (IOException e) { + FancyVisuals.getFancyLogger().error("Failed to get nametag for id " + id); + FancyVisuals.getFancyLogger().error(e); + } + + return nametag; + } + + @Override + public void removeNametag(@NotNull String id) { + jdb.delete(context.getName() + "/" + id); + } + + @Override + public @NotNull List getNametags() { + List nametags = new ArrayList<>(); + + try { + jdb.getAll(context.getName(), Nametag.class); + } catch (IOException e) { + FancyVisuals.getFancyLogger().error("Failed to get all nametags"); + FancyVisuals.getFancyLogger().error(e); + } + + return nametags; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametag.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametag.java new file mode 100644 index 00000000..33df40ed --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametag.java @@ -0,0 +1,165 @@ +package de.oliver.fancyvisuals.nametags.visibility; + +import de.oliver.fancysitula.api.entities.FS_Display; +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.entities.FS_TextDisplay; +import de.oliver.fancysitula.factories.FancySitula; +import de.oliver.fancyvisuals.FancyVisuals; +import de.oliver.fancyvisuals.api.nametags.Nametag; +import de.oliver.fancyvisuals.playerConfig.PlayerConfig; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.joml.Vector3f; +import org.lushplugins.chatcolorhandler.ModernChatColorHandler; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class PlayerNametag { + + private final Nametag nametag; + private final Player player; + private final Set viewers; + private FS_TextDisplay fsTextDisplay; + + public PlayerNametag(Nametag nametag, Player player) { + this.nametag = nametag; + this.player = player; + this.viewers = new HashSet<>(); + this.fsTextDisplay = new FS_TextDisplay(); + } + + public void updateVisibilityForAll() { + cleanViewers(); + + for (Player viewer : Bukkit.getOnlinePlayers()) { + boolean should = shouldBeVisibleTo(viewer); + boolean is = isVisibleTo(viewer); + + if (should && !is) { + showTo(viewer); + } else if (!should && is) { + hideFrom(viewer); + } + } + + } + + private boolean shouldBeVisibleTo(Player viewer) { + if (!player.isOnline()) { + return false; + } + + if (!viewer.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + if (player.getUniqueId().equals(viewer.getUniqueId())) { + PlayerConfig playerConfig = FancyVisuals.get().getPlayerConfigStore().getPlayerConfig(player.getUniqueId()); + if (!playerConfig.showOwnNametag()) { + return false; + } + } + + boolean dead = player.isDead(); + if (dead) { + return false; + } + + boolean inDistance = isInDistance(viewer.getLocation(), player.getLocation(), 24); + if (!inDistance) { + return false; + } + + return true; + } + + public void showTo(Player viewer) { + viewers.add(viewer.getUniqueId()); + + FS_RealPlayer fsViewer = new FS_RealPlayer(viewer); + FancySitula.ENTITY_FACTORY.spawnEntityFor(fsViewer, fsTextDisplay); + updateFor(viewer); + letDisplayRidePlayer(viewer); + } + + public void hideFrom(Player viewer) { + viewers.remove(viewer.getUniqueId()); + + FS_RealPlayer fsViewer = new FS_RealPlayer(viewer); + FancySitula.ENTITY_FACTORY.despawnEntityFor(fsViewer, fsTextDisplay); + } + + public void updateFor(Player viewer) { + fsTextDisplay.setTranslation(new Vector3f(0, 0.2f, 0)); + + fsTextDisplay.setBillboard(FS_Display.Billboard.CENTER); + + Color bgColor = Color.fromARGB((int) Long.parseLong(nametag.backgroundColor().substring(1), 16)); + fsTextDisplay.setBackground(bgColor.asARGB()); + + fsTextDisplay.setStyleFlags((byte) 0); + + fsTextDisplay.setShadow(nametag.textShadow()); + + switch (nametag.textAlignment()) { + case LEFT -> fsTextDisplay.setAlignLeft(true); + case RIGHT -> fsTextDisplay.setAlignRight(true); + case CENTER -> { + fsTextDisplay.setAlignLeft(false); + fsTextDisplay.setAlignRight(false); + } + } + + StringBuilder text = new StringBuilder(); + for (String line : nametag.textLines()) { + text.append(line).append('\n'); + } + text.deleteCharAt(text.length() - 1); + + fsTextDisplay.setText(ModernChatColorHandler.translate(text.toString(), player)); + + FS_RealPlayer fsViewer = new FS_RealPlayer(viewer); + FancySitula.ENTITY_FACTORY.setEntityDataFor(fsViewer, fsTextDisplay); + } + + public void letDisplayRidePlayer(Player viewer) { + FS_RealPlayer fsViewer = new FS_RealPlayer(viewer); + + FancySitula.PACKET_FACTORY.createSetPassengersPacket( + viewer.getEntityId(), + List.of(fsTextDisplay.getId()) + ).send(fsViewer); + } + + public boolean isVisibleTo(Player viewer) { + return viewers.contains(viewer.getUniqueId()); + } + + public void cleanViewers() { + viewers.removeIf(uuid -> { + Player player = Bukkit.getPlayer(uuid); + return player == null || !player.isOnline() || !player.getWorld().getName().equals(this.player.getWorld().getName()); + }); + } + + public Nametag getNametag() { + return nametag; + } + + public Player getPlayer() { + return player; + } + + public Set getViewers() { + return viewers; + } + + private boolean isInDistance(Location loc1, Location loc2, double distance) { + return loc1.distanceSquared(loc2) <= distance * distance; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametagScheduler.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametagScheduler.java new file mode 100644 index 00000000..8a029d30 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/nametags/visibility/PlayerNametagScheduler.java @@ -0,0 +1,57 @@ +package de.oliver.fancyvisuals.nametags.visibility; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import de.oliver.fancyvisuals.utils.distributedWorkload.DistributedWorkload; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class PlayerNametagScheduler { + + /** + * ScheduledExecutorService instance responsible for scheduling periodic execution of + * the DistributedWorkload. It manages the timing and frequency + * of workload distribution, ensuring that tasks are run at fixed intervals. + */ + private final ScheduledExecutorService schedulerExecutor; + + /** + * DistributedWorkload instance responsible for managing and executing tasks related + * to PlayerNametag objects. It divides the tasks across multiple buckets and performs + * specified actions on each element. Actions include updating visibility and checking + * whether a PlayerNametag needs to be updated. + */ + private final DistributedWorkload workload; + + public PlayerNametagScheduler(ExecutorService workerExecutor, int bucketSize) { + this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setNameFormat("PlayerNametagScheduler") + .build() + ); + + this.workload = new DistributedWorkload<>( + "PlayerNametagWorkload", + PlayerNametag::updateVisibilityForAll, + (nt) -> !nt.getPlayer().isOnline(), + bucketSize, + workerExecutor + ); + } + + /** + * Initializes the PlayerNametagScheduler and starts the periodic execution + * of the DistributedWorkload. The workload is scheduled to + * run at a fixed rate with an initial delay of 0 seconds and a period of + * 25 seconds between subsequent executions. + */ + public void init() { + schedulerExecutor.scheduleWithFixedDelay(workload, 1000, 250, TimeUnit.MILLISECONDS); + } + + public void add(PlayerNametag nametag) { + workload.addValue(() -> nametag); + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/JsonPlayerConfigStore.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/JsonPlayerConfigStore.java new file mode 100644 index 00000000..d88b44da --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/JsonPlayerConfigStore.java @@ -0,0 +1,100 @@ +package de.oliver.fancyvisuals.playerConfig; + +import de.oliver.fancylib.jdb.JDB; +import de.oliver.fancyvisuals.FancyVisuals; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * The {@code JsonPlayerConfigStore} class is responsible for handling player configuration storage and retrieval using JSON. + * It interacts with an underlying JSON database to manage {@code PlayerConfig} instances for individual players. + */ +public class JsonPlayerConfigStore { + + private static final String BASE_PATH = "plugins/FancyVisuals/data/player-configs/"; + private static final PlayerConfig DEFAULT_PLAYER_CONFIG = new PlayerConfig(true); + private final JDB jdb; + + public JsonPlayerConfigStore() { + jdb = new JDB(BASE_PATH); + + // Generate default player config if not present + getDefaultPlayerConfig(); + } + + + /** + * Retrieves the PlayerConfig for a specific player identified by their UUID. + * If the PlayerConfig is not found, the default PlayerConfig is returned. + * + * @param uuid the unique identifier of the player whose configuration is being retrieved. + * @return the PlayerConfig associated with the given UUID, or the default PlayerConfig if none is found. + */ + public @NotNull PlayerConfig getPlayerConfig(@NotNull UUID uuid) { + PlayerConfig playerConfig = null; + + try { + playerConfig = jdb.get(uuid.toString(), PlayerConfig.class); + } catch (Exception e) { + FancyVisuals.getFancyLogger().error("Failed to get player config for uuid " + uuid); + FancyVisuals.getFancyLogger().error(e); + } + + return playerConfig != null ? playerConfig : getDefaultPlayerConfig(); + } + + /** + * Sets the configuration for a specific player identified by the UUID. + * + * @param uuid the unique identifier of the player for whom the configuration is to be set + * @param playerConfig the PlayerConfig object containing the new configuration settings for the player + */ + public void setPlayerConfig(@NotNull UUID uuid, @NotNull PlayerConfig playerConfig) { + try { + jdb.set(uuid.toString(), playerConfig); + } catch (Exception e) { + FancyVisuals.getFancyLogger().error("Failed to set player config for uuid " + uuid); + FancyVisuals.getFancyLogger().error(e); + } + } + + /** + * Deletes the player configuration associated with the specified UUID. + * + * @param uuid the unique identifier of the player whose configuration is to be deleted + */ + public void deletePlayerConfig(@NotNull UUID uuid) { + jdb.delete(uuid.toString()); + } + + /** + * Retrieves the default PlayerConfig. + * If the default PlayerConfig is not found in the database, it sets and returns the predefined default configuration. + * + * @return the default PlayerConfig. If not present, the predefined default PlayerConfig is set and returned. + */ + public PlayerConfig getDefaultPlayerConfig() { + PlayerConfig playerConfig = null; + + try { + playerConfig = jdb.get("default", PlayerConfig.class); + } catch (Exception e) { + FancyVisuals.getFancyLogger().error("Failed to get default player config"); + FancyVisuals.getFancyLogger().error(e); + } + + if (playerConfig == null) { + playerConfig = DEFAULT_PLAYER_CONFIG; + + try { + jdb.set("default", DEFAULT_PLAYER_CONFIG); + } catch (Exception e) { + FancyVisuals.getFancyLogger().error("Failed to set default player config"); + FancyVisuals.getFancyLogger().error(e); + } + } + + return playerConfig; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/PlayerConfig.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/PlayerConfig.java new file mode 100644 index 00000000..3d296deb --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/playerConfig/PlayerConfig.java @@ -0,0 +1,14 @@ +package de.oliver.fancyvisuals.playerConfig; + +import com.google.gson.annotations.SerializedName; + +/** + * Represents the configuration settings for a player. + * + * @param showOwnNametag indicates whether the player should see their own nametag. + */ +public record PlayerConfig( + @SerializedName("show_own_nametag") + boolean showOwnNametag +) { +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/VaultHelper.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/VaultHelper.java new file mode 100644 index 00000000..c409f9d4 --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/VaultHelper.java @@ -0,0 +1,30 @@ +package de.oliver.fancyvisuals.utils; + +import net.milkbowl.vault.permission.Permission; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; + + +public class VaultHelper { + + private static boolean vaultLoaded = false; + private static Permission permission; + + public static void loadVault() { + vaultLoaded = Bukkit.getPluginManager().getPlugin("Vault") != null; + if (!vaultLoaded) { + return; + } + + RegisteredServiceProvider rsp = Bukkit.getServer().getServicesManager().getRegistration(Permission.class); + permission = rsp == null ? null : rsp.getProvider(); + } + + public static boolean isVaultLoaded() { + return vaultLoaded; + } + + public static Permission getPermission() { + return permission; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/Bucket.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/Bucket.java new file mode 100644 index 00000000..72d8d20e --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/Bucket.java @@ -0,0 +1,64 @@ +package de.oliver.fancyvisuals.utils.distributedWorkload; + +import java.util.LinkedList; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * A Bucket provides storage for entries, which are suppliers that yield elements of the + * specified type. It allows adding entries and performing actions on them, with optional + * asynchronous execution. + * + * @param The type of the elements in the bucket + */ +public class Bucket { + + private final String name; + private final LinkedList> entries; + + /** + * Constructs an empty Bucket. + */ + public Bucket(String name) { + this.name = name; + this.entries = new LinkedList<>(); + } + + /** + * Adds a new entry to the bucket. + * + * @param entry the supplier providing the entry to be added to the bucket + */ + public void addEntry(Supplier entry) { + entries.add(entry); + } + + /** + * Executes an action for each entry in the bucket. If the action is set to be + * executed asynchronously, it will run in separate threads; otherwise, it will + * execute in the current thread. + * + * @param action the action to be performed on each entry + * @param escape a condition to determine which entries should be removed after the action is performed + * @param executor the executor service to be used for asynchronous execution + */ + public void executeAction(Consumer action, Predicate escape, ExecutorService executor) { + LinkedList> suppliers = new LinkedList<>(entries); + + for (Supplier supplier : suppliers) { + executor.submit(() -> action.accept(supplier.get())); + } + + entries.removeIf(s -> escape.test(s.get())); + } + + public int size() { + return entries.size(); + } + + public String getName() { + return name; + } +} diff --git a/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/DistributedWorkload.java b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/DistributedWorkload.java new file mode 100644 index 00000000..39bf41db --- /dev/null +++ b/plugins/fancyvisuals/src/main/java/de/oliver/fancyvisuals/utils/distributedWorkload/DistributedWorkload.java @@ -0,0 +1,100 @@ +package de.oliver.fancyvisuals.utils.distributedWorkload; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * DistributedWorkload is a class that manages and executes a workload distributed across multiple buckets. + * Each bucket contains a subset of the workload and executes a specified action on its elements. + * + * @param The type of the elements in the workload + */ +public class DistributedWorkload implements Runnable { + + private final String workloadName; + private final Consumer action; + private final Predicate escapeCondition; + private final int bucketSize; + private final ExecutorService executorService; + + private final List> buckets; + private int currentBucket; + + /** + * Creates a new DistributedWorkload instance. + * + * @param workloadName the name of the workload + * @param action the action to be performed on each element of the workload + * @param escapeCondition a condition to determine which elements should be removed after the action is performed + * @param bucketSize the number of buckets into which the workload will be split + * @param executorService the executor service to be used for asynchronous execution + */ + public DistributedWorkload(String workloadName, Consumer action, Predicate escapeCondition, int bucketSize, ExecutorService executorService) { + this.workloadName = workloadName; + this.action = action; + this.escapeCondition = escapeCondition; + this.bucketSize = bucketSize; + this.executorService = executorService; + + this.currentBucket = 0; + this.buckets = new ArrayList<>(bucketSize); + for (int i = 0; i < bucketSize; i++) { + this.buckets.add(new Bucket<>("DWL-" + workloadName + "-" + i)); + } + } + + /** + * Executes the next bucket of workload by invoking the runNextBucket method. + * This method is called when this instance is run as a Runnable. + * Each bucket contains a portion of the workload and executes the specified action + * on each of its elements, either synchronously or asynchronously, + * depending on the configuration of the DistributedWorkload. + */ + @Override + public void run() { + runNextBucket(); + } + + /** + * Adds a new value to the smallest bucket within the distributed workload. + * The value is supplied by the specified Supplier. + * + * @param valueSupplier the supplier providing the value to be added to the bucket + */ + public void addValue(Supplier valueSupplier) { + Bucket smallestBucket = buckets.getFirst(); + + for (int i = 1; i < bucketSize; i++) { + if (smallestBucket.size() == 0) { + break; + } + + if (buckets.get(i).size() < smallestBucket.size()) { + smallestBucket = buckets.get(i); + } + } + + smallestBucket.addEntry(valueSupplier); + + } + + /** + * Advances to the next bucket in the list and executes its action. + * If the current bucket is the last one in the list, it wraps around to the first bucket. + * The action is executed on each element of the current bucket according to the configured + * conditions and can be run asynchronously if specified. + */ + private void runNextBucket() { + currentBucket++; + if (currentBucket >= buckets.size()) { + currentBucket = 0; + } + + Bucket bucket = buckets.get(currentBucket); + bucket.executeAction(action, escapeCondition, executorService); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ef793ba8..4d367a6a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,9 @@ include(":plugins:fancyholograms:implementation_1_20_2") include(":plugins:fancyholograms:implementation_1_20_1") include(":plugins:fancyholograms:implementation_1_19_4") +include(":plugins:fancyvisuals") +include(":plugins:fancyvisuals:api") + include(":libraries:common") include(":libraries:jdb") include(":libraries:plugin-tests")