diff --git a/plugins/fancyholograms-v2/Jenkinsfile b/plugins/fancyholograms-v2/Jenkinsfile new file mode 100644 index 00000000..3215173c --- /dev/null +++ b/plugins/fancyholograms-v2/Jenkinsfile @@ -0,0 +1,70 @@ +/* + Required env: java 21, git + Required plugins: discord notifier + Required credentials: MODRINTH_PUBLISH_API_TOKEN, HANGAR_PUBLISH_API_TOKEN +*/ + +pipeline { + agent any + + environment { + GRADLE_OPTS = '-Dorg.gradle.daemon=false' + } + + stages { + stage('Checkout') { + steps { + git url: 'https://github.com/FancyMcPlugins/fancyplugins', branch: 'main' + } + } + + stage('Build') { + steps { + sh 'chmod +x gradlew' + sh './gradlew clean :plugins:fancyholograms-v2:shadowJar' + echo 'Built the plugin!' + } + } + + stage('Deploy') { + steps { + // Load the secrets and make them available as environment variables + withCredentials([ + string(credentialsId: 'MODRINTH_PUBLISH_API_TOKEN', variable: 'MODRINTH_PUBLISH_API_TOKEN'), + string(credentialsId: 'HANGAR_PUBLISH_API_TOKEN', variable: 'HANGAR_PUBLISH_API_TOKEN') + ]) { + sh 'export MODRINTH_PUBLISH_API_TOKEN=${MODRINTH_PUBLISH_API_TOKEN} && ./gradlew :plugins:fancyholograms-v2:modrinth' + echo 'Published to Modrinth!' + + sh 'export HANGAR_PUBLISH_API_TOKEN=${HANGAR_PUBLISH_API_TOKEN} && ./gradlew :plugins:fancyholograms-v2:publishAllPublicationsToHangar' + echo 'Published to Hangar!' + } + } + } + } + + post { + always { + archiveArtifacts artifacts: 'plugins/fancyholograms-v2/build/libs/FancyHolograms-*.jar', allowEmptyArchive: true + } + success { + withCredentials([ + string(credentialsId: 'DISC_WEBHOOK_URL', variable: 'DISC_WEBHOOK_URL') + ]) { + discordSend description: "**Build:** ${env.BUILD_NUMBER} \n**Status:** ${currentBuild.currentResult} \n**Download:** https://modrinth.com/plugin/fancyholograms/versions", + footer: "Jenkins Pipeline", link: env.BUILD_URL, result: 'SUCCESS', title: "FancyHolograms #${env.BUILD_NUMBER}", webhookURL: "${DISC_WEBHOOK_URL}" + } + echo 'Build was successful!' + } + failure { + script { + withCredentials([ + string(credentialsId: 'DISC_WEBHOOK_URL', variable: 'DISC_WEBHOOK_URL') + ]) { + discordSend description: "**Build:** ${env.BUILD_NUMBER} \n**Status:** ${currentBuild.currentResult}", footer: "Jenkins Pipeline", link: env.BUILD_URL, result: 'FAILURE', title: "FancyHolograms #${env.BUILD_NUMBER}", "${DISC_WEBHOOK_URL}" + } + } + echo 'Build failed!' + } + } +} diff --git a/plugins/fancyholograms-v2/README.md b/plugins/fancyholograms-v2/README.md new file mode 100644 index 00000000..d4d2a5f5 --- /dev/null +++ b/plugins/fancyholograms-v2/README.md @@ -0,0 +1,145 @@ +
+ +![Banner](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/banner.png?raw=true) + +[![GitHub Release](https://img.shields.io/github/v/release/FancyMcPlugins/FancyHolograms?logo=github&labelColor=%2324292F&color=%23454F5A)](https://github.com/FancyMcPlugins/FancyHolograms/releases/latest) +[![Supports Folia](https://img.shields.io/badge/folia-supported-%23F9D879?labelColor=%2313154E&color=%234A44A6)](https://papermc.io/software/folia) +[![Discord](https://img.shields.io/discord/899740810956910683?cacheSeconds=3600&logo=discord&logoColor=white&label=%20&labelColor=%235865F2&color=%23707BF4)](https://discord.gg/ZUgYCEJUEx) +[![GitHub Downloads](https://img.shields.io/github/downloads/FancyMcPlugins/FancyHolograms/total?logo=github&labelColor=%2324292F&color=%23454F5A)](https://github.com/FancyMcPlugins/FancyHolograms/releases/latest) +[![Modrinth Downloads](https://img.shields.io/modrinth/dt/fancyholograms?logo=modrinth&logoColor=white&label=downloads&labelColor=%23139549&color=%2318c25f)](https://modrinth.com/plugin/fancyholograms) +[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/FancyMcPlugins/FancyHolograms?logo=codefactor&logoColor=white&label=%20)](https://www.codefactor.io/repository/github/fancymcplugins/fancyholograms/issues/main) + +[![Modrinth](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/compact/available/modrinth_vector.svg)](https://modrinth.com/plugin/fancyholograms) +[![Hangar](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/compact/available/hangar_vector.svg)](https://hangar.papermc.io/Oliver/FancyHolograms) + +
+ +Simple, lightweight and feature-rich hologram plugin for **[Paper](https://papermc.io/software/paper)** ( +and [Folia](https://papermc.io/software/folia)) servers using [display entities](https://minecraft.wiki/w/Display) +and packets. + +
+ +## Features + +With this plugin you can create holograms with customizable properties like: + +- **Hologram Type** (text, item or block) +- **Position**, **Rotation** and **Scale** +- **Text Alignment**, **Background Color** and **Shadow**. +- **Billboard** (fixed, center, horizontal, vertical) +- **MiniMessage** formatting. +- Placeholders support through [PlaceholderAPI](https://github.com/PlaceholderAPI/PlaceholderAPI) + and [MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) integration. +- [FancyNpcs](ttps://github.com/FancyMcPlugins/FancyNpcs) integration. +- ...and much more! + +Check out **[images section](#images)** down below. + +
+ +## Installation + +Paper **1.19.4** - **1.21.4** with **Java 21** (or higher) is required. Plugin should also work on **Paper** forks. + +**Spigot** is **not** supported. + +### Download (Stable) + +- **[Hangar](https://hangar.papermc.io/Oliver/FancyHolograms)** +- **[Modrinth](https://modrinth.com/plugin/fancyholograms)** +- **[GitHub Releases](https://github.com/FancyMcPlugins/FancyHolograms/releases)** + +### Download (Development Builds) + +- **[Jenkins CI](https://jenkins.fancyplugins.de/job/FancyHolograms/)** +- **[FancyPlugins Website](https://fancyplugins.de/FancyHolograms/download)** + +
+ +## Documentation + +Official documentation is hosted **[here](https://fancyplugins.de/docs/fancyholograms.html)**. Quick reference: + +- **[Getting Started](https://fancyplugins.de/docs/fh-getting-started.html)** +- **[Command Reference](https://fancyplugins.de/docs/fh-commands.html)** +- **[Using API](https://fancyplugins.de/docs/fh-api.html)** + +**Have more questions?** Feel free to ask them on our **[Discord](https://discord.gg/ZUgYCEJUEx)** server. + +
+ +## Developer API + +More information can be found in **[Documentation](https://fancyplugins.de/docs/fh-api.html)** +and [Javadocs](https://fancyplugins.de/javadocs/fancyholograms/). + +### Maven + +```xml + + + fancyplugins-releases + FancyPlugins Repository + https://repo.fancyplugins.de/releases + +``` + +```xml + + + de.oliver + FancyHolograms + [VERSION] + provided + +``` + +### Gradle + +```groovy +repositories { + maven("https://repo.fancyplugins.de/releases") +} + +dependencies { + compileOnly("de.oliver:FancyHolograms:[VERSION]") +} +``` + +
+ +## Building + +Follow these steps to build the plugin locally: + +```shell +# Cloning repository. +$ git clone https://github.com/FancyMcPlugins/FancyHolograms.git +# Entering cloned repository. +$ cd FancyHolograms +# Compiling and building artifacts. +$ gradlew shadowJar +# Once successfully built, plugin .jar can be found in /build/libs directory. +``` + +
+ +## Images + +Images showcasing the plugin, sent to us by our community. + +![Screenshot 1](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example1.jpeg?raw=true) +Provided by [@OliverSchlueter](https://github.com/OliverSchlueter) + +![Screenshot 2](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example2.jpeg?raw=true) +Provided by [@OliverSchlueter](https://github.com/OliverSchlueter) + +![Screenshot 3](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example3.jpeg?raw=true) +Provided by [@OliverSchlueter](https://github.com/OliverSchlueter) + +![Screenshot 4](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example4.jpeg?raw=true) +Provided by [@OliverSchlueter](https://github.com/OliverSchlueter) + +![Screenshot 5](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example5.jpeg?raw=true) +Provided by [@OliverSchlueter](https://github.com/OliverSchlueter) diff --git a/plugins/fancyholograms-v2/api/build.gradle.kts b/plugins/fancyholograms-v2/api/build.gradle.kts new file mode 100644 index 00000000..d82c957b --- /dev/null +++ b/plugins/fancyholograms-v2/api/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + id("java-library") + id("maven-publish") + id("com.gradleup.shadow") +} + +val minecraftVersion = "1.19.4" + +dependencies { + compileOnly("io.papermc.paper:paper-api:$minecraftVersion-R0.1-SNAPSHOT") + + compileOnly("de.oliver:FancyLib:36") + compileOnly("de.oliver.FancyAnalytics:logger:0.0.6") + + implementation("org.lushplugins:ChatColorHandler:5.1.2") +} + +tasks { + shadowJar { + relocate("org.lushplugins.chatcolorhandler", "de.oliver.fancyholograms.libs.chatcolorhandler") + + archiveClassifier.set("") + } + + 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 = "FancyHolograms" + version = project.version.toString() + from(project.components["java"]) + } + } + } + + java { + withSourcesJar() + withJavadocJar() + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + + options.release.set(17) + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/FancyHologramsPlugin.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/FancyHologramsPlugin.java new file mode 100644 index 00000000..82cd3da7 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/FancyHologramsPlugin.java @@ -0,0 +1,91 @@ +package de.oliver.fancyholograms.api; + +import de.oliver.fancyanalytics.logger.ExtendedFancyLogger; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.concurrent.ScheduledExecutorService; + +public interface FancyHologramsPlugin { + + static FancyHologramsPlugin get() { + if (isEnabled()) { + return EnabledChecker.getPlugin(); + } + + throw new NullPointerException("Plugin is not enabled"); + } + + static boolean isEnabled() { + return EnabledChecker.isFancyHologramsEnabled(); + } + + JavaPlugin getPlugin(); + + ExtendedFancyLogger getFancyLogger(); + + HologramManager getHologramManager(); + + /** + * Returns the configuration of the plugin. + * + * @return The configuration. + */ + HologramConfiguration getHologramConfiguration(); + + /** + * Sets the configuration of the plugin. + * + * @param configuration The new configuration. + * @param reload Whether the configuration should be reloaded. + */ + void setHologramConfiguration(HologramConfiguration configuration, boolean reload); + + /** + * @return The hologram storage. + */ + HologramStorage getHologramStorage(); + + /** + * @return The hologram thread + */ + ScheduledExecutorService getHologramThread(); + + /** + * Sets the hologram storage. + * + * @param storage The new hologram storage. + * @param reload Whether the current hologram cache should be reloaded. + */ + void setHologramStorage(HologramStorage storage, boolean reload); + + class EnabledChecker { + + private static Boolean enabled; + private static FancyHologramsPlugin plugin; + + public static Boolean isFancyHologramsEnabled() { + if (enabled != null) return enabled; + + Plugin pl = Bukkit.getPluginManager().getPlugin("FancyHolograms"); + + if (pl != null && pl.isEnabled()) { + try { + plugin = (FancyHologramsPlugin) pl; + } catch (ClassCastException e) { + throw new IllegalStateException("API failed to access plugin, if using the FancyHolograms API make sure to set the dependency to compile only."); + } + + enabled = true; + return true; + } + + return false; + } + + public static FancyHologramsPlugin getPlugin() { + return plugin; + } + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramConfiguration.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramConfiguration.java new file mode 100644 index 00000000..5f2f349b --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramConfiguration.java @@ -0,0 +1,69 @@ +package de.oliver.fancyholograms.api; + +import org.jetbrains.annotations.NotNull; + +public interface HologramConfiguration { + + /** + * Reloads the configuration. + * + * @param plugin The plugin instance. + */ + void reload(@NotNull FancyHologramsPlugin plugin); + + /** + * Returns whether version notifications are muted. + * + * @return {@code true} if version notifications are muted, {@code false} otherwise. + */ + boolean areVersionNotificationsMuted(); + + /** + * Returns whether autosave is enabled. + * + * @return {@code true} if autosave is enabled, {@code false} otherwise. + */ + boolean isAutosaveEnabled(); + + /** + * Returns the interval at which autosave is performed. + * + * @return The autosave interval in minutes. + */ + int getAutosaveInterval(); + + /** + * Returns whether the plugin should save holograms when they are changed. + * + * @return {@code true} if the plugin should save holograms when they are changed, {@code false} otherwise. + */ + boolean isSaveOnChangedEnabled(); + + /** + * Returns the default visibility distance for holograms. + * + * @return The default hologram visibility distance. + */ + int getDefaultVisibilityDistance(); + + /** + * Returns whether the plugin should register its commands. + * + * @return {@code true} if the plugin should register its commands, {@code false} otherwise. + */ + boolean isRegisterCommands(); + + /** + * Returns the log level for the plugin. + * + * @return The log level for the plugin. + */ + String getLogLevel(); + + /** + * Returns the interval at which hologram visibility is updated. + * + * @return The hologram visibility update interval in milliseconds. + */ + int getUpdateVisibilityInterval(); +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramManager.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramManager.java new file mode 100644 index 00000000..483077b7 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramManager.java @@ -0,0 +1,29 @@ +package de.oliver.fancyholograms.api; + +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; + +import java.util.Collection; +import java.util.Optional; + +public interface HologramManager { + + Optional getHologram(String name); + + Collection getPersistentHolograms(); + + Collection getHolograms(); + + void addHologram(Hologram hologram); + + void removeHologram(Hologram hologram); + + Hologram create(HologramData hologramData); + + void loadHolograms(); + + void saveHolograms(); + + void reloadHolograms(); + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramStorage.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramStorage.java new file mode 100644 index 00000000..f545fc74 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/HologramStorage.java @@ -0,0 +1,45 @@ +package de.oliver.fancyholograms.api; + +import de.oliver.fancyholograms.api.hologram.Hologram; + +import java.util.Collection; + +public interface HologramStorage { + + /** + * Saves a collection of holograms. + * + * @param holograms The holograms to save. + * @param override Whether to override existing holograms. + */ + void saveBatch(Collection holograms, boolean override); + + /** + * Saves a hologram. + * + * @param hologram The hologram to save. + */ + void save(Hologram hologram); + + /** + * Deletes a hologram. + * + * @param hologram The hologram to delete. + */ + void delete(Hologram hologram); + + /** + * Loads all holograms from all worlds + * + * @return A collection of all loaded holograms. + */ + Collection loadAll(); + + /** + * Loads all holograms from a specific world + * + * @param world The world to load the holograms from. + * @return A collection of all loaded holograms. + */ + Collection loadAll(String world); +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/BlockHologramData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/BlockHologramData.java new file mode 100644 index 00000000..70c46b68 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/BlockHologramData.java @@ -0,0 +1,72 @@ +package de.oliver.fancyholograms.api.data; + +import de.oliver.fancyholograms.api.hologram.HologramType; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; + +import java.util.Objects; + +public class BlockHologramData extends DisplayHologramData { + + public static Material DEFAULT_BLOCK = Material.GRASS_BLOCK; + + private Material block = DEFAULT_BLOCK; + + /** + * @param name Name of hologram + * @param location Location of hologram + * Default values are already set + */ + public BlockHologramData(String name, Location location) { + super(name, HologramType.BLOCK, location); + } + + public Material getBlock() { + return block; + } + + public BlockHologramData setBlock(Material block) { + if (!Objects.equals(this.block, block)) { + this.block = block; + setHasChanges(true); + } + + return this; + } + + @Override + public boolean read(ConfigurationSection section, String name) { + super.read(section, name); + block = Material.getMaterial(section.getString("block", "GRASS_BLOCK").toUpperCase()); + + return true; + } + + @Override + public boolean write(ConfigurationSection section, String name) { + super.write(section, name); + section.set("block", block.name()); + + return true; + } + + @Override + public BlockHologramData copy(String name) { + BlockHologramData blockHologramData = new BlockHologramData(name, getLocation()); + blockHologramData + .setBlock(this.getBlock()) + .setScale(this.getScale()) + .setShadowRadius(this.getShadowRadius()) + .setShadowStrength(this.getShadowStrength()) + .setBillboard(this.getBillboard()) + .setTranslation(this.getTranslation()) + .setBrightness(this.getBrightness()) + .setVisibilityDistance(getVisibilityDistance()) + .setVisibility(this.getVisibility()) + .setPersistent(this.isPersistent()) + .setLinkedNpcName(getLinkedNpcName()); + + return blockHologramData; + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java new file mode 100644 index 00000000..5ae62f6c --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java @@ -0,0 +1,211 @@ +package de.oliver.fancyholograms.api.data; + +import de.oliver.fancyholograms.api.hologram.HologramType; +import org.bukkit.Location; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Display; +import org.jetbrains.annotations.ApiStatus; +import org.joml.Vector3f; + +import java.util.Locale; +import java.util.Objects; + +public class DisplayHologramData extends HologramData { + + public static final Display.Billboard DEFAULT_BILLBOARD = Display.Billboard.CENTER; + public static final Vector3f DEFAULT_SCALE = new Vector3f(1, 1, 1); + public static final Vector3f DEFAULT_TRANSLATION = new Vector3f(0, 0, 0); + public static final float DEFAULT_SHADOW_RADIUS = 0.0f; + public static final float DEFAULT_SHADOW_STRENGTH = 1.0f; + public static final int DEFAULT_INTERPOLATION_DURATION = 0; + + private Display.Billboard billboard = DEFAULT_BILLBOARD; + private Vector3f scale = new Vector3f(DEFAULT_SCALE); + private Vector3f translation = new Vector3f(DEFAULT_TRANSLATION); + private Display.Brightness brightness; + private float shadowRadius = DEFAULT_SHADOW_RADIUS; + private float shadowStrength = DEFAULT_SHADOW_STRENGTH; + private int interpolationDuration = DEFAULT_INTERPOLATION_DURATION; + + /** + * @param name Name of hologram + * @param type Type of hologram + * @param location Location of hologram + * Default values are already set + */ + public DisplayHologramData(String name, HologramType type, Location location) { + super(name, type, location); + } + + public Display.Billboard getBillboard() { + return billboard; + } + + public DisplayHologramData setBillboard(Display.Billboard billboard) { + if (!Objects.equals(this.billboard, billboard)) { + this.billboard = billboard; + setHasChanges(true); + } + + return this; + } + + public Vector3f getScale() { + return scale; + } + + public DisplayHologramData setScale(Vector3f scale) { + if (!Objects.equals(this.scale, scale)) { + this.scale = scale; + setHasChanges(true); + } + + return this; + } + + public Vector3f getTranslation() { + return translation; + } + + public DisplayHologramData setTranslation(Vector3f translation) { + if (!Objects.equals(this.translation, translation)) { + this.translation = translation; + setHasChanges(true); + } + + return this; + } + + public Display.Brightness getBrightness() { + return brightness; + } + + public DisplayHologramData setBrightness(Display.Brightness brightness) { + if (!Objects.equals(this.brightness, brightness)) { + this.brightness = brightness; + setHasChanges(true); + } + + return this; + } + + public float getShadowRadius() { + return shadowRadius; + } + + public DisplayHologramData setShadowRadius(float shadowRadius) { + if (this.shadowRadius != shadowRadius) { + this.shadowRadius = shadowRadius; + setHasChanges(true); + } + + return this; + } + + public float getShadowStrength() { + return shadowStrength; + } + + public DisplayHologramData setShadowStrength(float shadowStrength) { + if (this.shadowStrength != shadowStrength) { + this.shadowStrength = shadowStrength; + setHasChanges(true); + } + + return this; + } + + @ApiStatus.Experimental + public int getInterpolationDuration() { + return interpolationDuration; + } + + @ApiStatus.Experimental + public DisplayHologramData setInterpolationDuration(int interpolationDuration) { + if (this.interpolationDuration != interpolationDuration) { + this.interpolationDuration = interpolationDuration; + setHasChanges(true); + } + + return this; + } + + @Override + public boolean read(ConfigurationSection section, String name) { + super.read(section, name); + scale = new Vector3f( + (float) section.getDouble("scale_x", DEFAULT_SCALE.x), + (float) section.getDouble("scale_y", DEFAULT_SCALE.y), + (float) section.getDouble("scale_z", DEFAULT_SCALE.z) + ); + + translation = new Vector3f( + (float) section.getDouble("translation_x", DEFAULT_TRANSLATION.x), + (float) section.getDouble("translation_y", DEFAULT_TRANSLATION.y), + (float) section.getDouble("translation_z", DEFAULT_TRANSLATION.z) + ); + + shadowRadius = (float) section.getDouble("shadow_radius", DEFAULT_SHADOW_RADIUS); + shadowStrength = (float) section.getDouble("shadow_strength", DEFAULT_SHADOW_STRENGTH); + + String billboardStr = section.getString("billboard", DEFAULT_BILLBOARD.name()); + billboard = switch (billboardStr.toLowerCase()) { + case "fixed" -> Display.Billboard.FIXED; + case "vertical" -> Display.Billboard.VERTICAL; + case "horizontal" -> Display.Billboard.HORIZONTAL; + default -> Display.Billboard.CENTER; + }; + + int blockBrightness = Math.min(15, section.getInt("block_brightness", -1)); + int skyBrightness = Math.min(15, section.getInt("sky_brightness", -1)); + + if(blockBrightness > -1 || skyBrightness > -1) { + brightness = new Display.Brightness( + Math.max(0, blockBrightness), + Math.max(0, skyBrightness) + ); + } + + return true; + } + + @Override + public boolean write(ConfigurationSection section, String name) { + super.write(section, name); + section.set("scale_x", scale.x); + section.set("scale_y", scale.y); + section.set("scale_z", scale.z); + section.set("translation_x", translation.x); + section.set("translation_y", translation.y); + section.set("translation_z", translation.z); + section.set("shadow_radius", shadowRadius); + section.set("shadow_strength", shadowStrength); + + if(brightness != null) { + section.set("block_brightness", brightness.getBlockLight()); + section.set("sky_brightness", brightness.getSkyLight()); + } + + section.set("billboard", billboard != Display.Billboard.CENTER ? billboard.name().toLowerCase(Locale.ROOT) : null); + + return true; + } + + @Override + public DisplayHologramData copy(String name) { + DisplayHologramData displayHologramData = new DisplayHologramData(name, getType(), getLocation()); + displayHologramData + .setScale(this.getScale()) + .setShadowRadius(this.getShadowRadius()) + .setShadowStrength(this.getShadowStrength()) + .setBillboard(this.getBillboard()) + .setTranslation(this.getTranslation()) + .setBrightness(this.getBrightness()) + .setVisibilityDistance(this.getVisibilityDistance()) + .setVisibility(this.getVisibility()) + .setPersistent(this.isPersistent()) + .setLinkedNpcName(this.getLinkedNpcName()); + + return displayHologramData; + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/HologramData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/HologramData.java new file mode 100644 index 00000000..3c14aa36 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/HologramData.java @@ -0,0 +1,190 @@ +package de.oliver.fancyholograms.api.data; + +import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.data.property.Visibility; +import de.oliver.fancyholograms.api.hologram.HologramType; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +public class HologramData implements YamlData { + + public static final int DEFAULT_VISIBILITY_DISTANCE = -1; + public static final Visibility DEFAULT_VISIBILITY = Visibility.ALL; + public static final boolean DEFAULT_IS_VISIBLE = true; + public static final boolean DEFAULT_PERSISTENCE = true; + + private final String name; + private final HologramType type; + private Location location; + private boolean hasChanges; + private int visibilityDistance = DEFAULT_VISIBILITY_DISTANCE; + private Visibility visibility = DEFAULT_VISIBILITY; + private boolean persistent = DEFAULT_PERSISTENCE; + private String linkedNpcName; + + /** + * @param name Name of hologram + * @param type Type of hologram + * @param location Location of hologram + * Default values are already set + */ + public HologramData(String name, HologramType type, Location location) { + this.name = name; + this.type = type; + this.location = location; + } + + public @NotNull String getName() { + return name; + } + + public @NotNull HologramType getType() { + return type; + } + + public @NotNull Location getLocation() { + return location.clone(); + } + + public HologramData setLocation(@Nullable Location location) { + this.location = location != null ? location.clone() : null; + setHasChanges(true); + return this; + } + + /** + * @return Whether the hologram needs to send an update to players + */ + public final boolean hasChanges() { + return hasChanges; + } + + /** + * @param hasChanges Whether the hologram needs to send an update to players + */ + public final void setHasChanges(boolean hasChanges) { + this.hasChanges = hasChanges; + } + + public int getVisibilityDistance() { + if (visibilityDistance > 0) { + return visibilityDistance; + } + + return FancyHologramsPlugin.get().getHologramConfiguration().getDefaultVisibilityDistance(); + } + + public HologramData setVisibilityDistance(int visibilityDistance) { + this.visibilityDistance = visibilityDistance; + setHasChanges(true); + return this; + } + + /** + * Get the type of visibility for the hologram. + * + * @return type of visibility. + */ + public Visibility getVisibility() { + return this.visibility; + } + + /** + * Set the type of visibility for the hologram. + */ + public HologramData setVisibility(@NotNull Visibility visibility) { + if (!Objects.equals(this.visibility, visibility)) { + this.visibility = visibility; + setHasChanges(true); + + if (this.visibility.equals(Visibility.MANUAL)) { + Visibility.ManualVisibility.clear(); + } + } + + return this; + } + + public boolean isPersistent() { + return persistent; + } + + public HologramData setPersistent(boolean persistent) { + this.persistent = persistent; + return this; + } + + public String getLinkedNpcName() { + return linkedNpcName; + } + + public HologramData setLinkedNpcName(String linkedNpcName) { + if (!Objects.equals(this.linkedNpcName, linkedNpcName)) { + this.linkedNpcName = linkedNpcName; + setHasChanges(true); + } + + return this; + } + + @Override + public boolean read(ConfigurationSection section, String name) { + String worldName = section.getString("location.world", "world"); + float x = (float) section.getDouble("location.x", 0); + float y = (float) section.getDouble("location.y", 0); + float z = (float) section.getDouble("location.z", 0); + float yaw = (float) section.getDouble("location.yaw", 0); + float pitch = (float) section.getDouble("location.pitch", 0); + + World world = Bukkit.getWorld(worldName); + if (world == null) { + FancyHologramsPlugin.get().getFancyLogger().warn("Could not load hologram '" + name + "', because the world '" + worldName + "' is not loaded"); + return false; + } + + location = new Location(world, x, y, z, yaw, pitch); + visibilityDistance = section.getInt("visibility_distance", DEFAULT_VISIBILITY_DISTANCE); + visibility = Optional.ofNullable(section.getString("visibility")) + .flatMap(Visibility::byString) + .orElseGet(() -> { + final var visibleByDefault = section.getBoolean("visible_by_default", DisplayHologramData.DEFAULT_IS_VISIBLE); + return visibleByDefault ? Visibility.ALL : Visibility.PERMISSION_REQUIRED; + }); + linkedNpcName = section.getString("linkedNpc"); + + return true; + } + + @Override + public boolean write(ConfigurationSection section, String name) { + section.set("type", type.name()); + section.set("location.world", location.getWorld().getName()); + section.set("location.x", location.x()); + section.set("location.y", location.y()); + section.set("location.z", location.z()); + section.set("location.yaw", location.getYaw()); + section.set("location.pitch", location.getPitch()); + + section.set("visibility_distance", visibilityDistance); + section.set("visibility", visibility.name()); + section.set("persistent", persistent); + section.set("linkedNpc", linkedNpcName); + + return true; + } + + public HologramData copy(String name) { + return new HologramData(name, type, this.getLocation()) + .setVisibilityDistance(this.getVisibilityDistance()) + .setVisibility(this.getVisibility()) + .setPersistent(this.isPersistent()) + .setLinkedNpcName(this.getLinkedNpcName()); + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/ItemHologramData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/ItemHologramData.java new file mode 100644 index 00000000..5c311e20 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/ItemHologramData.java @@ -0,0 +1,73 @@ +package de.oliver.fancyholograms.api.data; + +import de.oliver.fancyholograms.api.hologram.HologramType; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; + +import java.util.Objects; + +public class ItemHologramData extends DisplayHologramData { + + public static final ItemStack DEFAULT_ITEM = new ItemStack(Material.APPLE); + + private ItemStack item = DEFAULT_ITEM; + + /** + * @param name Name of hologram + * @param location Location of hologram + * Default values are already set + */ + public ItemHologramData(String name, Location location) { + super(name, HologramType.ITEM, location); + } + + public ItemStack getItemStack() { + return item; + } + + public ItemHologramData setItemStack(ItemStack item) { + if (!Objects.equals(this.item, item)) { + this.item = item; + setHasChanges(true); + } + + return this; + } + + @Override + public boolean read(ConfigurationSection section, String name) { + super.read(section, name); + item = section.getItemStack("item", DEFAULT_ITEM); + + return true; + } + + @Override + public boolean write(ConfigurationSection section, String name) { + super.write(section, name); + section.set("item", item); + + return true; + } + + @Override + public ItemHologramData copy(String name) { + ItemHologramData itemHologramData = new ItemHologramData(name, getLocation()); + itemHologramData + .setItemStack(this.getItemStack()) + .setScale(this.getScale()) + .setShadowRadius(this.getShadowRadius()) + .setShadowStrength(this.getShadowStrength()) + .setBillboard(this.getBillboard()) + .setTranslation(this.getTranslation()) + .setBrightness(this.getBrightness()) + .setVisibilityDistance(this.getVisibilityDistance()) + .setVisibility(this.getVisibility()) + .setPersistent(this.isPersistent()) + .setLinkedNpcName(this.getLinkedNpcName()); + + return itemHologramData; + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/TextHologramData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/TextHologramData.java new file mode 100644 index 00000000..83f64213 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/TextHologramData.java @@ -0,0 +1,212 @@ +package de.oliver.fancyholograms.api.data; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.hologram.HologramType; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.TextDisplay; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +public class TextHologramData extends DisplayHologramData { + + public static final TextDisplay.TextAlignment DEFAULT_TEXT_ALIGNMENT = TextDisplay.TextAlignment.CENTER; + public static final boolean DEFAULT_TEXT_SHADOW_STATE = false; + public static final boolean DEFAULT_SEE_THROUGH = false; + public static final int DEFAULT_TEXT_UPDATE_INTERVAL = -1; + + private List text; + private Color background; + private TextDisplay.TextAlignment textAlignment = DEFAULT_TEXT_ALIGNMENT; + private boolean textShadow = DEFAULT_TEXT_SHADOW_STATE; + private boolean seeThrough = DEFAULT_SEE_THROUGH; + private int textUpdateInterval = DEFAULT_TEXT_UPDATE_INTERVAL; + + /** + * @param name Name of hologram + * @param location Location of hologram + * Default values are already set + */ + public TextHologramData(String name, Location location) { + super(name, HologramType.TEXT, location); + text = new ArrayList<>(List.of("Edit this line with /hologram edit " + name)); + } + + public List getText() { + return text; + } + + public TextHologramData setText(List text) { + if (!Objects.equals(this.text, text)) { + this.text = text; + setHasChanges(true); + } + + return this; + } + + public void addLine(String line) { + text.add(line); + setHasChanges(true); + } + + public void removeLine(int index) { + text.remove(index); + setHasChanges(true); + } + + public Color getBackground() { + return background; + } + + public TextHologramData setBackground(Color background) { + if (!Objects.equals(this.background, background)) { + this.background = background; + setHasChanges(true); + } + + return this; + } + + public TextDisplay.TextAlignment getTextAlignment() { + return textAlignment; + } + + public TextHologramData setTextAlignment(TextDisplay.TextAlignment textAlignment) { + if (!Objects.equals(this.textAlignment, textAlignment)) { + this.textAlignment = textAlignment; + setHasChanges(true); + } + + return this; + } + + public boolean hasTextShadow() { + return textShadow; + } + + public TextHologramData setTextShadow(boolean textShadow) { + if (this.textShadow != textShadow) { + this.textShadow = textShadow; + setHasChanges(true); + } + + return this; + } + + public boolean isSeeThrough() { + return seeThrough; + } + + public TextHologramData setSeeThrough(boolean seeThrough) { + if (this.seeThrough != seeThrough) { + this.seeThrough = seeThrough; + setHasChanges(true); + } + + return this; + } + + public int getTextUpdateInterval() { + return textUpdateInterval; + } + + public TextHologramData setTextUpdateInterval(int textUpdateInterval) { + if (this.textUpdateInterval != textUpdateInterval) { + this.textUpdateInterval = textUpdateInterval; + setHasChanges(true); + } + + return this; + } + + @Override + public boolean read(ConfigurationSection section, String name) { + super.read(section, name); + text = section.getStringList("text"); + if (text.isEmpty()) { + text = List.of("Could not load hologram text"); + //TODO: maybe return false here? + } + + textShadow = section.getBoolean("text_shadow", DEFAULT_TEXT_SHADOW_STATE); + seeThrough = section.getBoolean("see_through", DEFAULT_SEE_THROUGH); + textUpdateInterval = section.getInt("update_text_interval", DEFAULT_TEXT_UPDATE_INTERVAL); + + String textAlignmentStr = section.getString("text_alignment", DEFAULT_TEXT_ALIGNMENT.name().toLowerCase()); + textAlignment = switch (textAlignmentStr.toLowerCase(Locale.ROOT)) { + case "right" -> TextDisplay.TextAlignment.RIGHT; + case "left" -> TextDisplay.TextAlignment.LEFT; + default -> TextDisplay.TextAlignment.CENTER; + }; + + background = null; + String backgroundStr = section.getString("background", null); + if (backgroundStr != null) { + if (backgroundStr.equalsIgnoreCase("transparent")) { + background = Hologram.TRANSPARENT; + } else if (backgroundStr.startsWith("#")) { + background = Color.fromARGB((int) Long.parseLong(backgroundStr.substring(1), 16)); + //backwards compatibility, make rgb hex colors solid color -their alpha is 0 by default- + if (backgroundStr.length() == 7) background = background.setAlpha(255); + } else { + background = Color.fromARGB(NamedTextColor.NAMES.value(backgroundStr.toLowerCase(Locale.ROOT).trim().replace(' ', '_')).value() | 0xC8000000); + } + } + + return true; + } + + @Override + public boolean write(ConfigurationSection section, String name) { + super.write(section, name); + section.set("text", text); + section.set("text_shadow", textShadow); + section.set("see_through", seeThrough); + section.set("text_alignment", textAlignment.name().toLowerCase(Locale.ROOT)); + section.set("update_text_interval", textUpdateInterval); + + final String color; + if (background == null) { + color = null; + } else if (background == Hologram.TRANSPARENT) { + color = "transparent"; + } else { + NamedTextColor named = background.getAlpha() == 255 ? NamedTextColor.namedColor(background.asRGB()) : null; + color = named != null ? named.toString() : '#' + Integer.toHexString(background.asARGB()); + } + + section.set("background", color); + + return true; + } + + @Override + public TextHologramData copy(String name) { + TextHologramData textHologramData = new TextHologramData(name, getLocation()); + textHologramData + .setText(this.getText()) + .setBackground(this.getBackground()) + .setTextAlignment(this.getTextAlignment()) + .setTextShadow(this.hasTextShadow()) + .setSeeThrough(this.isSeeThrough()) + .setTextUpdateInterval(this.getTextUpdateInterval()) + .setScale(this.getScale()) + .setShadowRadius(this.getShadowRadius()) + .setShadowStrength(this.getShadowStrength()) + .setBillboard(this.getBillboard()) + .setTranslation(this.getTranslation()) + .setBrightness(this.getBrightness()) + .setVisibilityDistance(this.getVisibilityDistance()) + .setVisibility(this.getVisibility()) + .setPersistent(this.isPersistent()) + .setLinkedNpcName(this.getLinkedNpcName()); + + return textHologramData; + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/YamlData.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/YamlData.java new file mode 100644 index 00000000..1ca34e35 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/YamlData.java @@ -0,0 +1,20 @@ +package de.oliver.fancyholograms.api.data; + +import org.bukkit.configuration.ConfigurationSection; + +public interface YamlData { + + /** + * Reads the data from the given configuration section. + * + * @return Whether the data was read successfully. + */ + boolean read(ConfigurationSection section, String name); + + /** + * Writes the data to the given configuration section. + * + * @return Whether the data was written successfully. + */ + boolean write(ConfigurationSection section, String name); +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/property/Visibility.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/property/Visibility.java new file mode 100644 index 00000000..3e7b2133 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/data/property/Visibility.java @@ -0,0 +1,88 @@ +package de.oliver.fancyholograms.api.data.property; + +import com.google.common.collect.HashMultimap; +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; + +public enum Visibility { + /** + * Everybody can see a hologram. + */ + ALL((player, hologram) -> true), + /** + * The player needs permission to see a specific hologram. + */ + PERMISSION_REQUIRED( + (player, hologram) -> player.hasPermission("fancyholograms.viewhologram." + hologram.getData().getName()) + ), + /** + * The player needs to be added manually through the API + */ + MANUAL(ManualVisibility::canSee); + + private final VisibilityPredicate predicate; + + Visibility(VisibilityPredicate predicate) { + this.predicate = predicate; + } + + public static Optional byString(String value) { + return Arrays.stream(Visibility.values()) + .filter(visibility -> visibility.toString().equalsIgnoreCase(value)) + .findFirst(); + } + + public boolean canSee(Player player, Hologram hologram) { + return this.predicate.canSee(player, hologram); + } + + @FunctionalInterface + public interface VisibilityPredicate { + boolean canSee(Player player, Hologram hologram); + } + + /** + * Handling of Visibility.MANUAL + *
+ * TODO: Discussion needed - Potentially condense this into one singular multimap within the enum? + */ + public static class ManualVisibility { + private static final HashMultimap distantViewers = HashMultimap.create(); + + public static boolean canSee(Player player, Hologram hologram) { + return hologram.isViewer(player) || distantViewers.containsEntry(hologram.getName(), player.getUniqueId()); + } + + public static void addDistantViewer(Hologram hologram, UUID uuid) { + addDistantViewer(hologram.getName(), uuid); + } + + public static void addDistantViewer(String hologramName, UUID uuid) { + distantViewers.put(hologramName, uuid); + } + + public static void removeDistantViewer(Hologram hologram, UUID uuid) { + removeDistantViewer(hologram.getName(), uuid); + } + + public static void removeDistantViewer(String hologramName, UUID uuid) { + distantViewers.remove(hologramName, uuid); + } + + public static void remove(Hologram hologram) { + remove(hologram.getName()); + } + + public static void remove(String hologramName) { + distantViewers.removeAll(hologramName); + } + + public static void clear() { + distantViewers.clear(); + } + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramCreateEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramCreateEvent.java new file mode 100644 index 00000000..27238540 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramCreateEvent.java @@ -0,0 +1,38 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hologram is being created, any hologram data changed will be reflected in the new hologram + */ +public final class HologramCreateEvent extends HologramEvent { + + private static final HandlerList handlerList = new HandlerList(); + + + @NotNull + private final Player player; + + public HologramCreateEvent(@NotNull final Hologram hologram, @NotNull final Player player) { + super(hologram, false); + + this.player = player; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull Player getPlayer() { + return this.player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramDeleteEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramDeleteEvent.java new file mode 100644 index 00000000..3f65478c --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramDeleteEvent.java @@ -0,0 +1,40 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hologram is being deleted, any hologram data changed will be reflected in the hologram if + * the event is called + */ +public final class HologramDeleteEvent extends HologramEvent { + + private static final HandlerList handlerList = new HandlerList(); + + + @NotNull + private final CommandSender player; + + public HologramDeleteEvent(@NotNull final Hologram hologram, @NotNull final CommandSender player) { + super(hologram, false); + + this.player = player; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull CommandSender getPlayer() { + return this.player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramEvent.java new file mode 100644 index 00000000..15090052 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramEvent.java @@ -0,0 +1,47 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a base event related to Holograms. This is an abstract class that other event classes related to Holograms should extend. + * This event is cancellable, which means it can be prevented from being processed by the server. + */ +public abstract class HologramEvent extends Event implements Cancellable { + + @NotNull + private final Hologram hologram; + + + private boolean cancelled; + + + protected HologramEvent(@NotNull final Hologram hologram, final boolean isAsync) { + super(isAsync); + this.hologram = hologram; + } + + + /** + * Returns the hologram involved in this event. + * + * @return the hologram involved in this event + */ + public final @NotNull Hologram getHologram() { + return this.hologram; + } + + + @Override + public final boolean isCancelled() { + return this.cancelled; + } + + @Override + public final void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramHideEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramHideEvent.java new file mode 100644 index 00000000..cdfb0a4d --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramHideEvent.java @@ -0,0 +1,39 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hologram is being hidden from a player + */ +public final class HologramHideEvent extends HologramEvent { + + private static final HandlerList handlerList = new HandlerList(); + + + @NotNull + private final Player player; + + public HologramHideEvent(@NotNull final Hologram hologram, @NotNull final Player player) { + super(hologram, !Bukkit.isPrimaryThread()); + + this.player = player; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull Player getPlayer() { + return this.player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramShowEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramShowEvent.java new file mode 100644 index 00000000..88e02927 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramShowEvent.java @@ -0,0 +1,39 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hologram is being shown to a player + */ +public final class HologramShowEvent extends HologramEvent { + + private static final HandlerList handlerList = new HandlerList(); + + + @NotNull + private final Player player; + + public HologramShowEvent(@NotNull final Hologram hologram, @NotNull final Player player) { + super(hologram, !Bukkit.isPrimaryThread()); + + this.player = player; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull Player getPlayer() { + return this.player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramUpdateEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramUpdateEvent.java new file mode 100644 index 00000000..016e9f0c --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramUpdateEvent.java @@ -0,0 +1,88 @@ +package de.oliver.fancyholograms.api.events; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.*; +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hologram is being updated, the data in the hologram is current and the event holds the new data + */ +public final class HologramUpdateEvent extends HologramEvent { + + private static final HandlerList handlerList = new HandlerList(); + + private final @NotNull CommandSender player; + private final @NotNull HologramData updatedData; + private final @NotNull HologramModification modification; + + public HologramUpdateEvent(@NotNull final Hologram hologram, @NotNull final CommandSender player, @NotNull final HologramData updatedData, @NotNull final HologramModification modification) { + super(hologram, false); + + this.player = player; + this.updatedData = updatedData; + this.modification = modification; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull CommandSender getPlayer() { + return this.player; + } + + /** + * Returns the current data of the hologram. + * + * @return the current data of the hologram + */ + public @NotNull HologramData getCurrentData() { + return getHologram().getData(); + } + + /** + * Returns the updated data of the hologram. + * + * @return the updated data of the hologram + */ + public @NotNull HologramData getUpdatedData() { + return this.updatedData; + } + + /** + * Returns the type of modification performed on the hologram. + * + * @return the type of modification performed on the hologram + */ + public @NotNull HologramModification getModification() { + return this.modification; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + + + /** + * Represents the various types of modifications that can be made to a Hologram. + */ + public enum HologramModification { + TEXT, + POSITION, + SCALE, + TRANSLATION, + BILLBOARD, + BACKGROUND, + TEXT_SHADOW, + TEXT_ALIGNMENT, + SEE_THROUGH, + SHADOW_RADIUS, + SHADOW_STRENGTH, + UPDATE_TEXT_INTERVAL, + UPDATE_VISIBILITY_DISTANCE; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsLoadedEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsLoadedEvent.java new file mode 100644 index 00000000..31b2ead6 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsLoadedEvent.java @@ -0,0 +1,35 @@ +package de.oliver.fancyholograms.api.events; + +import com.google.common.collect.ImmutableList; +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public final class HologramsLoadedEvent extends Event { + + private static final HandlerList handlerList = new HandlerList(); + + private final ImmutableList holograms; + + public HologramsLoadedEvent(@NotNull final ImmutableList holograms) { + super(!Bukkit.isPrimaryThread()); + + this.holograms = holograms; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull ImmutableList getManager() { + return this.holograms; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsUnloadedEvent.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsUnloadedEvent.java new file mode 100644 index 00000000..a3711f7a --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/events/HologramsUnloadedEvent.java @@ -0,0 +1,35 @@ +package de.oliver.fancyholograms.api.events; + +import com.google.common.collect.ImmutableList; +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public final class HologramsUnloadedEvent extends Event { + + private static final HandlerList handlerList = new HandlerList(); + + private final ImmutableList holograms; + + public HologramsUnloadedEvent(@NotNull final ImmutableList holograms) { + super(!Bukkit.isPrimaryThread()); + + this.holograms = holograms; + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public @NotNull ImmutableList getManager() { + return this.holograms; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/Hologram.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/Hologram.java new file mode 100644 index 00000000..e9f2319c --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/Hologram.java @@ -0,0 +1,372 @@ +package de.oliver.fancyholograms.api.hologram; + +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.data.property.Visibility; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.World; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lushplugins.chatcolorhandler.ModernChatColorHandler; + +import java.util.*; + +/** + * Abstract base class for creating, updating, and managing holograms. + *

+ * This class provides the basic functionality needed to work with holograms + * across multiple versions of Minecraft. To create a hologram specific to a version of Minecraft, + * extend this class and implement the abstract methods. + *

+ * Note that the specific way holograms are created, updated, and deleted + * will vary depending on the Minecraft version. + *

+ * A Hologram object includes data about the hologram and maintains a set of players to whom the hologram is shown. + */ +public abstract class Hologram { + + public static final int LINE_WIDTH = 1000; + public static final Color TRANSPARENT = Color.fromARGB(0); + protected static final int MINIMUM_PROTOCOL_VERSION = 762; + + protected final @NotNull HologramData data; + /** + * Set of UUIDs of players to whom the hologram is currently shown. + */ + protected final @NotNull Set viewers = new HashSet<>(); + + protected Hologram(@NotNull final HologramData data) { + this.data = data; + } + + @NotNull + public String getName() { + return data.getName(); + } + + public final @NotNull HologramData getData() { + return this.data; + } + + /** + * Returns the entity id of this hologram + * This id is for packet use only as the entity is not registered to the server + * @return entity id + */ + public abstract int getEntityId(); + + /** + * Returns the Display entity of this Hologram object. + * The entity is not registered in the world or server. + * Only use this method if you know what you're doing. + *

+ * This method will return null in 1.20.5 and newer versions + * + * @return the Display entity of this Hologram object + */ + @ApiStatus.Internal + @Deprecated(forRemoval = true, since = "2.4.1") + public abstract @Nullable Display getDisplayEntity(); + + protected abstract void create(); + + protected abstract void delete(); + + protected abstract void update(); + + protected abstract boolean show(@NotNull final Player player); + + protected abstract boolean hide(@NotNull final Player player); + + protected abstract void refresh(@NotNull final Player player); + + /** + * Create the hologram entity. + * Only run this if creating custom Hologram implementations as this is run in + * {@link de.oliver.fancyholograms.api.HologramManager#create(HologramData)}. + */ + public final void createHologram() { + create(); + } + + /** + * Deletes the hologram entity. + */ + public final void deleteHologram() { + delete(); + } + + /** + * Shows the hologram to a collection of players. + * Use {@link #forceShowHologram(Player)} if this hologram is not registered to the HologramManager. + * + * @param players The players to show the hologram to + */ + public final void showHologram(Collection players) { + players.forEach(this::showHologram); + } + + /** + * Shows the hologram to a player. + * Use {@link #forceShowHologram(Player)} if this hologram is not registered to the HologramManager. + * + * @param player The player to show the hologram to + */ + public final void showHologram(Player player) { + viewers.add(player.getUniqueId()); + } + + /** + * Forcefully shows the hologram to a player. + * + * @param player The player to show the hologram to + */ + public final void forceShowHologram(Player player) { + show(player); + + if (this.getData().getVisibility().equals(Visibility.MANUAL)) { + Visibility.ManualVisibility.addDistantViewer(this, player.getUniqueId()); + } + } + + /** + * Hides the hologram from a collection of players. + * Use {@link #forceHideHologram(Player)} if this hologram is not registered to the HologramManager. + * + * @param players The players to hide the hologram from + */ + public final void hideHologram(Collection players) { + players.forEach(this::hideHologram); + } + + /** + * Hides the hologram from a player. + * Use {@link #forceHideHologram(Player)} if this hologram is not registered to the HologramManager. + * + * @param player The player to hide the hologram from + */ + public final void hideHologram(Player player) { + viewers.remove(player.getUniqueId()); + } + + /** + * Forcefully hides the hologram from a player. + * + * @param player The player to show the hologram to + */ + public final void forceHideHologram(Player player) { + hide(player); + + if (this.getData().getVisibility().equals(Visibility.MANUAL)) { + Visibility.ManualVisibility.removeDistantViewer(this, player.getUniqueId()); + } + } + + /** + * Queues hologram to update and refresh for players. + * + * @deprecated in favour of {@link #queueUpdate()} + */ + @Deprecated(forRemoval = true) + public final void updateHologram() { + queueUpdate(); + } + + /** + * Queues hologram to update and refresh for players + * Use {@link #forceUpdate()} if this hologram is not registered to the HologramManager. + */ + public final void queueUpdate() { + data.setHasChanges(true); + } + + /** + * Forcefully updates and refreshes hologram for players. + */ + public final void forceUpdate() { + update(); + } + + /** + * Refreshes the hologram for the players currently viewing it. + */ + public void refreshForViewers() { + final var players = getViewers() + .stream() + .map(Bukkit::getPlayer) + .toList(); + + refreshHologram(players); + } + + /** + * Refreshes the hologram for players currently viewing it in the same world as the hologram. + */ + public void refreshForViewersInWorld() { + World world = data.getLocation().getWorld(); + final var players = getViewers() + .stream() + .map(Bukkit::getPlayer) + .filter(player -> player != null && player.getWorld().equals(world)) + .toList(); + + refreshHologram(players); + } + + /** + * Refreshes the hologram's data for a player. + * + * @param player the player to refresh for + */ + public final void refreshHologram(@NotNull final Player player) { + refresh(player); + } + + /** + * Refreshes the hologram's data for a collection of players. + * + * @param players the collection of players to refresh for + */ + public final void refreshHologram(@NotNull final Collection players) { + players.forEach(this::refreshHologram); + } + + /** + * @return a copy of the set of UUIDs of players currently viewing the hologram + */ + public final @NotNull Set getViewers() { + return new HashSet<>(this.viewers); + } + + /** + * @param player the player to check for + * @return whether the player is currently viewing the hologram + */ + public final boolean isViewer(@NotNull final Player player) { + return isViewer(player.getUniqueId()); + } + + /** + * @param player the uuid of the player to check for + * @return whether the player is currently viewing the hologram + */ + public final boolean isViewer(@NotNull final UUID player) { + return this.viewers.contains(player); + } + + protected boolean shouldShowTo(@NotNull final Player player) { + if (!meetsVisibilityConditions(player)) { + return false; + } + + return isWithinVisibilityDistance(player); + } + + public boolean meetsVisibilityConditions(@NotNull final Player player) { + return this.getData().getVisibility().canSee(player, this); + } + + public boolean isWithinVisibilityDistance(@NotNull final Player player) { + final var location = getData().getLocation(); + if (!location.getWorld().equals(player.getWorld())) { + return false; + } + + int visibilityDistance = data.getVisibilityDistance(); + double distanceSquared = location.distanceSquared(player.getLocation()); + + return distanceSquared <= visibilityDistance * visibilityDistance; + } + + /** + * Checks and updates the shown state for a player. + * If the hologram is shown and should not be, it hides it. + * If the hologram is not shown and should be, it shows it. + * Use {@link #forceUpdateShownStateFor(Player)} if this hologram is not registered to the HologramManager. + * + * @param player the player to check and update the shown state for + */ + public void updateShownStateFor(Player player) { + boolean isShown = isViewer(player); + boolean shouldBeShown = shouldShowTo(player); + + if (isShown && !shouldBeShown) { + showHologram(player); + } else if (!isShown && shouldBeShown) { + hideHologram(player); + } + } + + /** + * Checks and forcefully updates the shown state for a player. + * If the hologram is shown and should not be, it hides it. + * If the hologram is not shown and should be, it shows it. + * + * @param player the player to check and update the shown state for + */ + public void forceUpdateShownStateFor(Player player) { + boolean isShown = isViewer(player); + + if (meetsVisibilityConditions(player)) { + if (isWithinVisibilityDistance(player)) { + // Ran if the player meets the visibility conditions and is within visibility distance + if (!isShown) { + show(player); + + if (getData().getVisibility().equals(Visibility.MANUAL)) { + Visibility.ManualVisibility.removeDistantViewer(this, player.getUniqueId()); + } + } + } else { + // Ran if the player meets the visibility conditions but is not within visibility distance + if (isShown) { + hide(player); + + if (getData().getVisibility().equals(Visibility.MANUAL)) { + Visibility.ManualVisibility.addDistantViewer(this, player.getUniqueId()); + } + } + } + } else { + // Ran if the player does not meet visibility conditions + if (isShown) { + hide(player); + } + } + } + + /** + * Gets the text shown in the hologram. If a player is specified, placeholders in the text are replaced + * with their corresponding values for the player. + * + * @param player the player to get the placeholders for, or null if no placeholders should be replaced + * @return the text shown in the hologram + */ + public final Component getShownText(@Nullable final Player player) { + if (!(getData() instanceof TextHologramData textData)) { + return null; + } + + var text = String.join("\n", textData.getText()); + + return ModernChatColorHandler.translate(text, player); + } + + @Override + public final boolean equals(@Nullable final Object o) { + if (this == o) return true; + if (!(o instanceof Hologram that)) return false; + + return Objects.equals(this.getData(), that.getData()); + } + + @Override + public final int hashCode() { + return Objects.hash(this.getData()); + } +} diff --git a/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/HologramType.java b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/HologramType.java new file mode 100644 index 00000000..432fcdd8 --- /dev/null +++ b/plugins/fancyholograms-v2/api/src/main/java/de/oliver/fancyholograms/api/hologram/HologramType.java @@ -0,0 +1,31 @@ +package de.oliver.fancyholograms.api.hologram; + +import java.util.Arrays; +import java.util.List; + +public enum HologramType { + TEXT(Arrays.asList("background", "textshadow", "textalignment", "seethrough", "setline", "removeline", "addline", "insertbefore", "insertafter", "updatetextinterval")), + ITEM(List.of("item")), + BLOCK(List.of("block")); + + private final List commands; + + HologramType(List commands) { + this.commands = commands; + } + + public static HologramType getByName(String name) { + for (HologramType type : values()) { + if (type.name().equalsIgnoreCase(name)) { + return type; + } + } + + return null; + } + + public List getCommands() { + return commands; + } + +} diff --git a/plugins/fancyholograms-v2/build.gradle.kts b/plugins/fancyholograms-v2/build.gradle.kts new file mode 100644 index 00000000..cd0d70ce --- /dev/null +++ b/plugins/fancyholograms-v2/build.gradle.kts @@ -0,0 +1,209 @@ +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription +import net.minecrell.pluginyml.paper.PaperPluginDescription +import java.io.BufferedReader +import java.io.InputStreamReader + +plugins { + id("java-library") + id("maven-publish") + + id("xyz.jpenilla.run-paper") + id("com.gradleup.shadow") + id("net.minecrell.plugin-yml.paper") + id("io.papermc.hangar-publish-plugin") + id("com.modrinth.minotaur") +} + +runPaper.folia.registerTask() + +val supportedVersions = + listOf( + "1.19.4", + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + ) + +allprojects { + group = "de.oliver" + val buildId = System.getenv("BUILD_ID") + version = "2.4.2" + (if (buildId != null) ".$buildId" else "") + description = "Simple, lightweight and fast hologram plugin using display entities" + + repositories { + mavenLocal() + mavenCentral() + + maven(url = "https://repo.papermc.io/repository/maven-public/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + + maven(url = "https://repo.fancyplugins.de/snapshots") + maven(url = "https://repo.fancyplugins.de/releases") + maven(url = "https://repo.lushplugins.org/releases") + maven(url = "https://repo.viaversion.com/") + maven(url = "https://repo.opencollab.dev/main/") + } +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyholograms-v2:api")) + implementation(project(":plugins:fancyholograms-v2:implementation_1_20_4", configuration = "reobf")) + implementation(project(":plugins:fancyholograms-v2:implementation_1_20_2", configuration = "reobf")) + implementation(project(":plugins:fancyholograms-v2:implementation_1_20_1", configuration = "reobf")) + implementation(project(":plugins:fancyholograms-v2:implementation_1_19_4", configuration = "reobf")) + + implementation("de.oliver:FancyLib:36") + implementation("de.oliver:FancySitula:0.0.13") + implementation("de.oliver.FancyAnalytics:api:0.1.6") + implementation("de.oliver.FancyAnalytics:logger:0.0.6") + + compileOnly("de.oliver:FancyNpcs:2.4.2") + compileOnly("org.lushplugins:ChatColorHandler:5.1.2") + compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT") +} + +paper { + main = "de.oliver.fancyholograms.FancyHolograms" + bootstrapper = "de.oliver.fancyholograms.loaders.FancyHologramsBootstrapper" + loader = "de.oliver.fancyholograms.loaders.FancyHologramsLoader" + foliaSupported = true + version = rootProject.version.toString() + description = "Simple, lightweight and fast hologram plugin using display entities" + apiVersion = "1.19" + load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD + serverDependencies { + register("FancyNpcs") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("MiniPlaceholders") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("PlaceholderAPI") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + register("floodgate") { + required = false + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + joinClasspath = true + } + } +} + +tasks { + runServer { + minecraftVersion("1.21.4") + + downloadPlugins { + modrinth("fancynpcs", "2.4.0") + hangar("ViaVersion", "5.2.0") + hangar("ViaBackwards", "5.2.0") +// modrinth("multiverse-core", "4.3.11") + hangar("PlaceholderAPI", "2.11.6") +// modrinth("DecentHolograms", "2.8.12") + } + } + + shadowJar { + archiveClassifier.set("") + archiveBaseName.set("FancyHolograms") + + dependsOn(":plugins:fancyholograms-v2:api:shadowJar") + } + + 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 gitCommitHash.get(), + "build" to (System.getenv("BUILD_ID") ?: "").ifEmpty { "undefined" } + ) + + inputs.properties(props) + + filesMatching("paper-plugin.yml") { + expand(props) + } + + filesMatching("version.yml") { + expand(props) + } + } +} + +tasks.publishAllPublicationsToHangar { + dependsOn("shadowJar") +} + +tasks.modrinth { + dependsOn("shadowJar") +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +val gitCommitHash: Provider = providers.exec { + commandLine("git", "rev-parse", "HEAD") +}.standardOutput.asText.map { it.trim() } + +val gitCommitMessage: Provider = providers.exec { + commandLine("git", "log", "-1", "--pretty=%B") +}.standardOutput.asText.map { it.trim() } + +hangarPublish { + publications.register("plugin") { + version = project.version as String + id = "FancyHolograms" + channel = "Alpha" + + apiKey.set(System.getenv("HANGAR_PUBLISH_API_TOKEN")) + + platforms { + paper { + jar = tasks.shadowJar.flatMap { it.archiveFile } + platformVersions = supportedVersions + } + } + + changelog = gitCommitMessage.get() + } +} + +modrinth { + token.set(System.getenv("MODRINTH_PUBLISH_API_TOKEN")) + projectId.set("fancyholograms") + versionNumber.set(project.version.toString()) + versionType.set("alpha") + uploadFile.set(file("build/libs/${project.name}-${project.version}.jar")) + gameVersions.addAll(supportedVersions) + loaders.add("paper") + loaders.add("folia") + changelog.set(gitCommitMessage.get()) +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/images/banner.png b/plugins/fancyholograms-v2/images/banner.png new file mode 100644 index 00000000..60f5ec7a Binary files /dev/null and b/plugins/fancyholograms-v2/images/banner.png differ diff --git a/plugins/fancyholograms-v2/images/screenshots/example1.jpeg b/plugins/fancyholograms-v2/images/screenshots/example1.jpeg new file mode 100644 index 00000000..97cc97d3 Binary files /dev/null and b/plugins/fancyholograms-v2/images/screenshots/example1.jpeg differ diff --git a/plugins/fancyholograms-v2/images/screenshots/example2.jpeg b/plugins/fancyholograms-v2/images/screenshots/example2.jpeg new file mode 100644 index 00000000..7dd724e4 Binary files /dev/null and b/plugins/fancyholograms-v2/images/screenshots/example2.jpeg differ diff --git a/plugins/fancyholograms-v2/images/screenshots/example3.jpeg b/plugins/fancyholograms-v2/images/screenshots/example3.jpeg new file mode 100644 index 00000000..db23574a Binary files /dev/null and b/plugins/fancyholograms-v2/images/screenshots/example3.jpeg differ diff --git a/plugins/fancyholograms-v2/images/screenshots/example4.jpeg b/plugins/fancyholograms-v2/images/screenshots/example4.jpeg new file mode 100644 index 00000000..6f647d41 Binary files /dev/null and b/plugins/fancyholograms-v2/images/screenshots/example4.jpeg differ diff --git a/plugins/fancyholograms-v2/images/screenshots/example5.jpeg b/plugins/fancyholograms-v2/images/screenshots/example5.jpeg new file mode 100644 index 00000000..cce15620 Binary files /dev/null and b/plugins/fancyholograms-v2/images/screenshots/example5.jpeg differ diff --git a/plugins/fancyholograms-v2/implementation_1_19_4/build.gradle.kts b/plugins/fancyholograms-v2/implementation_1_19_4/build.gradle.kts new file mode 100644 index 00000000..0022b811 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_19_4/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + + +val minecraftVersion = "1.19.4" + + +dependencies { + paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyholograms-v2:api")) + implementation("de.oliver:FancyLib:36") + compileOnly("com.viaversion:viaversion-api:5.2.1") +} + + +tasks { + named("assemble") { + dependsOn(named("reobfJar")) + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + + options.release.set(17) + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_19_4.java b/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_19_4.java new file mode 100644 index 00000000..74837bd9 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_19_4.java @@ -0,0 +1,271 @@ +package de.oliver.fancyholograms.hologram.version; + +import com.mojang.math.Transformation; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramHideEvent; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancylib.ReflectionUtils; +import io.papermc.paper.adventure.PaperAdventure; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData.DataItem; +import net.minecraft.network.syncher.SynchedEntityData.DataValue; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.Display.TextDisplay; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; + +import java.util.ArrayList; + +import static de.oliver.fancylib.ReflectionUtils.getValue; + +public final class Hologram1_19_4 extends Hologram { + + @Nullable + private Display display; + + public Hologram1_19_4(@NotNull final HologramData data) { + super(data); + } + + @Override + public int getEntityId() { + return display.getId(); + } + + @Override + public @Nullable org.bukkit.entity.Display getDisplayEntity() { + return display != null ? (org.bukkit.entity.Display) display.getBukkitEntity() : null; + } + + @Override + public void create() { + final var location = data.getLocation(); + if (location.getWorld() == null) { + return; // no location data, cannot be created + } + + ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); + + switch (data.getType()) { + case TEXT -> this.display = new Display.TextDisplay(EntityType.TEXT_DISPLAY, world); + case BLOCK -> this.display = new Display.BlockDisplay(EntityType.BLOCK_DISPLAY, world); + case ITEM -> this.display = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, world); + } + + if (data instanceof DisplayHologramData dd) { + final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_19_4.DATA_INTERPOLATION_DURATION_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_DURATION_ID, dd.getInterpolationDuration()); + + final var DATA_INTERPOLATION_START_DELTA_TICKS_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_19_4.DATA_INTERPOLATION_START_DELTA_TICKS_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0); + } + + update(); + } + + @Override + public void delete() { + this.display = null; + } + + @Override + public void update() { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to update + } + + // location data + final var location = data.getLocation(); + if (!location.isWorldLoaded()) { + return; + } else { + display.setPosRaw(location.x(), location.y(), location.z()); + display.setYRot(location.getYaw()); + display.setXRot(location.getPitch()); + } + + if (display instanceof TextDisplay textDisplay && data instanceof TextHologramData textData) { + // line width + final var DATA_LINE_WIDTH_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_19_4.DATA_LINE_WIDTH_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_LINE_WIDTH_ID, Hologram.LINE_WIDTH); + + // background + final var DATA_BACKGROUND_COLOR_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_19_4.DATA_BACKGROUND_COLOR_ID.getMapping()); + + final var background = textData.getBackground(); + if (background == null) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND); + } else if (background == Hologram.TRANSPARENT) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, 0); + } else { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, background.asARGB()); + } + + // text shadow + if (textData.hasTextShadow()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SHADOW)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SHADOW)); + } + + // text alignment + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.LEFT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_LEFT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_LEFT)); + } + + // see through + if (textData.isSeeThrough()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SEE_THROUGH)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SEE_THROUGH)); + } + + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.RIGHT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_RIGHT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_RIGHT)); + } + + } else if (display instanceof Display.ItemDisplay itemDisplay && data instanceof ItemHologramData itemData) { + // item + itemDisplay.setItemStack(ItemStack.fromBukkitCopy(itemData.getItemStack())); + + } else if (display instanceof Display.BlockDisplay blockDisplay && data instanceof BlockHologramData blockData) { + Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.of("minecraft:" + blockData.getBlock().name().toLowerCase(), ':')); + blockDisplay.setBlockState(block.defaultBlockState()); + } + + if (data instanceof DisplayHologramData displayData) { + // billboard data + display.setBillboardConstraints(switch (displayData.getBillboard()) { + case FIXED -> Display.BillboardConstraints.FIXED; + case VERTICAL -> Display.BillboardConstraints.VERTICAL; + case HORIZONTAL -> Display.BillboardConstraints.HORIZONTAL; + case CENTER -> Display.BillboardConstraints.CENTER; + }); + + // brightness + if (displayData.getBrightness() != null) { + display.setBrightnessOverride(new Brightness(displayData.getBrightness().getBlockLight(), displayData.getBrightness().getSkyLight())); + } + + // entity scale AND MORE! + display.setTransformation(new Transformation( + displayData.getTranslation(), + new Quaternionf(), + displayData.getScale(), + new Quaternionf()) + ); + + // entity shadow + display.setShadowRadius(displayData.getShadowRadius()); + display.setShadowStrength(displayData.getShadowStrength()); + + // view range + display.setViewRange(displayData.getVisibilityDistance()); + } + } + + + @Override + public boolean show(@NotNull final Player player) { + if (!new HologramShowEvent(this, player).callEvent()) { + return false; + } + + if (this.display == null) { + create(); // try to create it if it doesn't exist every time + } + + final var display = this.display; + if (display == null) { + return false; // could not be created, nothing to show + } + + if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + // TODO: cache player protocol version + // TODO: fix this +// final var protocolVersion = FancyHologramsPlugin.get().isUsingViaVersion() ? Via.getAPI().getPlayerVersion(player) : MINIMUM_PROTOCOL_VERSION; +// if (protocolVersion < MINIMUM_PROTOCOL_VERSION) { +// return false; +// } + + serverPlayer.connection.send(new ClientboundAddEntityPacket(display)); + this.viewers.add(player.getUniqueId()); + refreshHologram(player); + + return true; + } + + @Override + public boolean hide(@NotNull final Player player) { + if (!new HologramHideEvent(this, player).callEvent()) { + return false; + } + + final var display = this.display; + if (display == null) { + return false; // doesn't exist, nothing to hide + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId())); + + this.viewers.remove(player.getUniqueId()); + return true; + } + + + @Override + public void refresh(@NotNull final Player player) { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to refresh + } + + if (!isViewer(player)) { + return; + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display)); + + if (display instanceof TextDisplay textDisplay) { + textDisplay.setText(PaperAdventure.asVanilla(getShownText(player))); + } + + final var values = new ArrayList>(); + + //noinspection unchecked + for (final var item : ((Int2ObjectMap>) getValue(display.getEntityData(), "e")).values()) { + values.add(item.value()); + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values)); + } + +} diff --git a/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_19_4.java b/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_19_4.java new file mode 100644 index 00000000..75115d4a --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_19_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_19_4.java @@ -0,0 +1,20 @@ +package de.oliver.fancyholograms.hologram.version; + +public enum MappingKeys1_19_4 { + + DATA_INTERPOLATION_DURATION_ID("r"), + DATA_INTERPOLATION_START_DELTA_TICKS_ID("q"), + DATA_LINE_WIDTH_ID("aL"), + DATA_BACKGROUND_COLOR_ID("aM"), + ; + + private final String mapping; + + MappingKeys1_19_4(String mapping) { + this.mapping = mapping; + } + + public String getMapping() { + return mapping; + } +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_1/build.gradle.kts b/plugins/fancyholograms-v2/implementation_1_20_1/build.gradle.kts new file mode 100644 index 00000000..adc4e1c9 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_1/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + + +val minecraftVersion = "1.20.1" + + +dependencies { + paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyholograms-v2:api")) + implementation("de.oliver:FancyLib:36") + compileOnly("com.viaversion:viaversion-api:5.2.1") +} + + +tasks { + named("assemble") { + dependsOn(named("reobfJar")) + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + + options.release.set(17) + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_1.java b/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_1.java new file mode 100644 index 00000000..d7afb5c9 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_1.java @@ -0,0 +1,271 @@ +package de.oliver.fancyholograms.hologram.version; + +import com.mojang.math.Transformation; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramHideEvent; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancylib.ReflectionUtils; +import io.papermc.paper.adventure.PaperAdventure; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData.DataItem; +import net.minecraft.network.syncher.SynchedEntityData.DataValue; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.Display.TextDisplay; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; + +import java.util.ArrayList; + +import static de.oliver.fancylib.ReflectionUtils.getValue; + +public final class Hologram1_20_1 extends Hologram { + + @Nullable + private Display display; + + public Hologram1_20_1(@NotNull final HologramData data) { + super(data); + } + + @Override + public int getEntityId() { + return display.getId(); + } + + @Override + public @Nullable org.bukkit.entity.Display getDisplayEntity() { + return display != null ? (org.bukkit.entity.Display) display.getBukkitEntity() : null; + } + + @Override + public void create() { + final var location = data.getLocation(); + if (!location.isWorldLoaded()) { + return; // no location data, cannot be created + } + + ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); + + switch (data.getType()) { + case TEXT -> this.display = new Display.TextDisplay(EntityType.TEXT_DISPLAY, world); + case BLOCK -> this.display = new Display.BlockDisplay(EntityType.BLOCK_DISPLAY, world); + case ITEM -> this.display = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, world); + } + + if (data instanceof DisplayHologramData dd){ + final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_1.DATA_INTERPOLATION_DURATION_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_DURATION_ID, dd.getInterpolationDuration()); + + final var DATA_INTERPOLATION_START_DELTA_TICKS_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_1.DATA_INTERPOLATION_START_DELTA_TICKS_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0); + } + + update(); + } + + @Override + public void delete() { + this.display = null; + } + + @Override + public void update() { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to update + } + + // location data + final var location = data.getLocation(); + if (location.getWorld() == null || !location.isWorldLoaded()) { + return; + } else { + display.setPosRaw(location.x(), location.y(), location.z()); + display.setYRot(location.getYaw()); + display.setXRot(location.getPitch()); + } + + if (display instanceof TextDisplay textDisplay && data instanceof TextHologramData textData) { + // line width + final var DATA_LINE_WIDTH_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_1.DATA_LINE_WIDTH_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_LINE_WIDTH_ID, Hologram.LINE_WIDTH); + + // background + final var DATA_BACKGROUND_COLOR_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_1.DATA_BACKGROUND_COLOR_ID.getMapping()); //DATA_BACKGROUND_COLOR_ID + + final var background = textData.getBackground(); + if (background == null) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND); + } else if (background == Hologram.TRANSPARENT) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, 0); + } else { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, background.asARGB()); + } + + // text shadow + if (textData.hasTextShadow()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SHADOW)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SHADOW)); + } + + // text alignment + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.LEFT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_LEFT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_LEFT)); + } + + // see through + if (textData.isSeeThrough()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SEE_THROUGH)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SEE_THROUGH)); + } + + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.RIGHT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_RIGHT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_RIGHT)); + } + + } else if (display instanceof Display.ItemDisplay itemDisplay && data instanceof ItemHologramData itemData) { + // item + itemDisplay.setItemStack(ItemStack.fromBukkitCopy(itemData.getItemStack())); + + } else if (display instanceof Display.BlockDisplay blockDisplay && data instanceof BlockHologramData blockData) { + Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.of("minecraft:" + blockData.getBlock().name().toLowerCase(), ':')); + blockDisplay.setBlockState(block.defaultBlockState()); + } + + if (data instanceof DisplayHologramData displayData) { + // billboard data + display.setBillboardConstraints(switch (displayData.getBillboard()) { + case FIXED -> Display.BillboardConstraints.FIXED; + case VERTICAL -> Display.BillboardConstraints.VERTICAL; + case HORIZONTAL -> Display.BillboardConstraints.HORIZONTAL; + case CENTER -> Display.BillboardConstraints.CENTER; + }); + + // brightness + if (displayData.getBrightness() != null) { + display.setBrightnessOverride(new Brightness(displayData.getBrightness().getBlockLight(), displayData.getBrightness().getSkyLight())); + } + + // entity scale AND MORE! + display.setTransformation(new Transformation( + displayData.getTranslation(), + new Quaternionf(), + displayData.getScale(), + new Quaternionf()) + ); + + // entity shadow + display.setShadowRadius(displayData.getShadowRadius()); + display.setShadowStrength(displayData.getShadowStrength()); + + // view range + display.setViewRange(displayData.getVisibilityDistance()); + } + } + + + @Override + public boolean show(@NotNull final Player player) { + if (!new HologramShowEvent(this, player).callEvent()) { + return false; + } + + if (this.display == null) { + create(); // try to create it if it doesn't exist every time + } + + final var display = this.display; + if (display == null) { + return false; // could not be created, nothing to show + } + + if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + // TODO: cache player protocol version + // TODO: fix this +// final var protocolVersion = FancyHologramsPlugin.get().isUsingViaVersion() ? Via.getAPI().getPlayerVersion(player) : MINIMUM_PROTOCOL_VERSION; +// if (protocolVersion < MINIMUM_PROTOCOL_VERSION) { +// return false; +// } + + serverPlayer.connection.send(new ClientboundAddEntityPacket(display)); + this.viewers.add(player.getUniqueId()); + refreshHologram(player); + + return true; + } + + @Override + public boolean hide(@NotNull final Player player) { + if (!new HologramHideEvent(this, player).callEvent()) { + return false; + } + + final var display = this.display; + if (display == null) { + return false; // doesn't exist, nothing to hide + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId())); + + this.viewers.remove(player.getUniqueId()); + return true; + } + + + @Override + public void refresh(@NotNull final Player player) { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to refresh + } + + if (!isViewer(player)) { + return; + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display)); + + if (display instanceof TextDisplay textDisplay) { + textDisplay.setText(PaperAdventure.asVanilla(getShownText(player))); + } + + final var values = new ArrayList>(); + + //noinspection unchecked + for (final var item : ((Int2ObjectMap>) getValue(display.getEntityData(), "e")).values()) { + values.add(item.value()); + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values)); + } + +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_1.java b/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_1.java new file mode 100644 index 00000000..9fffe629 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_1/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_1.java @@ -0,0 +1,20 @@ +package de.oliver.fancyholograms.hologram.version; + +public enum MappingKeys1_20_1 { + + DATA_INTERPOLATION_DURATION_ID("q"), + DATA_INTERPOLATION_START_DELTA_TICKS_ID("p"), + DATA_LINE_WIDTH_ID("aM"), + DATA_BACKGROUND_COLOR_ID("aN"), + ; + + private final String mapping; + + MappingKeys1_20_1(String mapping) { + this.mapping = mapping; + } + + public String getMapping() { + return mapping; + } +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_2/build.gradle.kts b/plugins/fancyholograms-v2/implementation_1_20_2/build.gradle.kts new file mode 100644 index 00000000..fc0daa42 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_2/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + + +val minecraftVersion = "1.20.2" + + +dependencies { + paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyholograms-v2:api")) + implementation("de.oliver:FancyLib:36") + compileOnly("com.viaversion:viaversion-api:5.2.1") +} + + +tasks { + named("assemble") { + dependsOn(named("reobfJar")) + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + + options.release.set(17) + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_2.java b/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_2.java new file mode 100644 index 00000000..f7d248fe --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_2.java @@ -0,0 +1,271 @@ +package de.oliver.fancyholograms.hologram.version; + +import com.mojang.math.Transformation; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramHideEvent; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancylib.ReflectionUtils; +import io.papermc.paper.adventure.PaperAdventure; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData.DataItem; +import net.minecraft.network.syncher.SynchedEntityData.DataValue; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.Display.TextDisplay; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; + +import java.util.ArrayList; + +import static de.oliver.fancylib.ReflectionUtils.getValue; + +public final class Hologram1_20_2 extends Hologram { + + @Nullable + private Display display; + + public Hologram1_20_2(@NotNull final HologramData data) { + super(data); + } + + @Override + public int getEntityId() { + return display.getId(); + } + + @Override + public @Nullable org.bukkit.entity.Display getDisplayEntity() { + return display != null ? (org.bukkit.entity.Display) display.getBukkitEntity() : null; + } + + @Override + public void create() { + final var location = data.getLocation(); + if (!location.isWorldLoaded()) { + return; // no location data, cannot be created + } + + ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); + + switch (data.getType()) { + case TEXT -> this.display = new Display.TextDisplay(EntityType.TEXT_DISPLAY, world); + case BLOCK -> this.display = new Display.BlockDisplay(EntityType.BLOCK_DISPLAY, world); + case ITEM -> this.display = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, world); + } + + if (data instanceof DisplayHologramData dd) { + final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_2.DATA_INTERPOLATION_DURATION_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_DURATION_ID, dd.getInterpolationDuration()); + + final var DATA_INTERPOLATION_START_DELTA_TICKS_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_2.DATA_INTERPOLATION_START_DELTA_TICKS_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0); + } + + update(); + } + + @Override + public void delete() { + this.display = null; + } + + @Override + public void update() { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to update + } + + // location data + final var location = data.getLocation(); + if (location.getWorld() == null || !location.isWorldLoaded()) { + return; + } else { + display.setPosRaw(location.x(), location.y(), location.z()); + display.setYRot(location.getYaw()); + display.setXRot(location.getPitch()); + } + + if (display instanceof TextDisplay textDisplay && data instanceof TextHologramData textData) { + // line width + final var DATA_LINE_WIDTH_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_2.DATA_LINE_WIDTH_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_LINE_WIDTH_ID, Hologram.LINE_WIDTH); + + // background + final var DATA_BACKGROUND_COLOR_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_2.DATA_BACKGROUND_COLOR_ID.getMapping()); + + final var background = textData.getBackground(); + if (background == null) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND); + } else if (background == Hologram.TRANSPARENT) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, 0); + } else { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, background.asARGB()); + } + + // text shadow + if (textData.hasTextShadow()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SHADOW)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SHADOW)); + } + + // text alignment + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.LEFT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_LEFT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_LEFT)); + } + + // see through + if (textData.isSeeThrough()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SEE_THROUGH)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SEE_THROUGH)); + } + + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.RIGHT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_RIGHT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_RIGHT)); + } + + } else if (display instanceof Display.ItemDisplay itemDisplay && data instanceof ItemHologramData itemData) { + // item + itemDisplay.setItemStack(ItemStack.fromBukkitCopy(itemData.getItemStack())); + + } else if (display instanceof Display.BlockDisplay blockDisplay && data instanceof BlockHologramData blockData) { + Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.of("minecraft:" + blockData.getBlock().name().toLowerCase(), ':')); + blockDisplay.setBlockState(block.defaultBlockState()); + } + + if (data instanceof DisplayHologramData displayData) { + // billboard data + display.setBillboardConstraints(switch (displayData.getBillboard()) { + case FIXED -> Display.BillboardConstraints.FIXED; + case VERTICAL -> Display.BillboardConstraints.VERTICAL; + case HORIZONTAL -> Display.BillboardConstraints.HORIZONTAL; + case CENTER -> Display.BillboardConstraints.CENTER; + }); + + // brightness + if (displayData.getBrightness() != null) { + display.setBrightnessOverride(new Brightness(displayData.getBrightness().getBlockLight(), displayData.getBrightness().getSkyLight())); + } + + // entity scale AND MORE! + display.setTransformation(new Transformation( + displayData.getTranslation(), + new Quaternionf(), + displayData.getScale(), + new Quaternionf()) + ); + + // entity shadow + display.setShadowRadius(displayData.getShadowRadius()); + display.setShadowStrength(displayData.getShadowStrength()); + + // view range + display.setViewRange(displayData.getVisibilityDistance()); + } + } + + + @Override + public boolean show(@NotNull final Player player) { + if (!new HologramShowEvent(this, player).callEvent()) { + return false; + } + + if (this.display == null) { + create(); // try to create it if it doesn't exist every time + } + + final var display = this.display; + if (display == null) { + return false; // could not be created, nothing to show + } + + if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + // TODO: cache player protocol version + // TODO: fix this +// final var protocolVersion = FancyHologramsPlugin.get().isUsingViaVersion() ? Via.getAPI().getPlayerVersion(player) : MINIMUM_PROTOCOL_VERSION; +// if (protocolVersion < MINIMUM_PROTOCOL_VERSION) { +// return false; +// } + + serverPlayer.connection.send(new ClientboundAddEntityPacket(display)); + this.viewers.add(player.getUniqueId()); + refreshHologram(player); + + return true; + } + + @Override + public boolean hide(@NotNull final Player player) { + if (!new HologramHideEvent(this, player).callEvent()) { + return false; + } + + final var display = this.display; + if (display == null) { + return false; // doesn't exist, nothing to hide + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId())); + + this.viewers.remove(player.getUniqueId()); + return true; + } + + + @Override + public void refresh(@NotNull final Player player) { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to refresh + } + + if (!isViewer(player)) { + return; + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display)); + + if (display instanceof TextDisplay textDisplay) { + textDisplay.setText(PaperAdventure.asVanilla(getShownText(player))); + } + + final var values = new ArrayList>(); + + //noinspection unchecked + for (final var item : ((Int2ObjectMap>) getValue(display.getEntityData(), "e")).values()) { + values.add(item.value()); + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values)); + } + +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_2.java b/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_2.java new file mode 100644 index 00000000..59ee90ee --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_2/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_2.java @@ -0,0 +1,20 @@ +package de.oliver.fancyholograms.hologram.version; + +public enum MappingKeys1_20_2 { + + DATA_INTERPOLATION_DURATION_ID("r"), + DATA_INTERPOLATION_START_DELTA_TICKS_ID("q"), + DATA_LINE_WIDTH_ID("aN"), + DATA_BACKGROUND_COLOR_ID("aO"), + ; + + private final String mapping; + + MappingKeys1_20_2(String mapping) { + this.mapping = mapping; + } + + public String getMapping() { + return mapping; + } +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_4/build.gradle.kts b/plugins/fancyholograms-v2/implementation_1_20_4/build.gradle.kts new file mode 100644 index 00000000..f75d64a3 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_4/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + + +val minecraftVersion = "1.20.4" + + +dependencies { + paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT") + + implementation(project(":plugins:fancyholograms-v2:api")) + implementation("de.oliver:FancyLib:36") + compileOnly("com.viaversion:viaversion-api:5.2.1") +} + + +tasks { + named("assemble") { + dependsOn(named("reobfJar")) + } + + javadoc { + options.encoding = Charsets.UTF_8.name() + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + + options.release.set(17) + } +} \ No newline at end of file diff --git a/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_4.java b/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_4.java new file mode 100644 index 00000000..4f1faaea --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/Hologram1_20_4.java @@ -0,0 +1,272 @@ +package de.oliver.fancyholograms.hologram.version; + +import com.mojang.math.Transformation; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramHideEvent; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancylib.ReflectionUtils; +import io.papermc.paper.adventure.PaperAdventure; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData.DataItem; +import net.minecraft.network.syncher.SynchedEntityData.DataValue; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.Display.TextDisplay; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; + +import java.util.ArrayList; + +import static de.oliver.fancylib.ReflectionUtils.getValue; + +public final class Hologram1_20_4 extends Hologram { + + @Nullable + private Display display; + + public Hologram1_20_4(@NotNull final HologramData data) { + super(data); + } + + @Override + public int getEntityId() { + return display.getId(); + } + + @Override + public @Nullable org.bukkit.entity.Display getDisplayEntity() { + return display != null ? (org.bukkit.entity.Display) display.getBukkitEntity() : null; + } + + @Override + public void create() { + final var location = data.getLocation(); + if (!location.isWorldLoaded()) { + return; // no location data, cannot be created + } + + ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); + + switch (data.getType()) { + case TEXT -> this.display = new Display.TextDisplay(EntityType.TEXT_DISPLAY, world); + case BLOCK -> this.display = new Display.BlockDisplay(EntityType.BLOCK_DISPLAY, world); + case ITEM -> this.display = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, world); + } + + if (data instanceof DisplayHologramData dd) { + final var DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_4.DISPLAY__DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID, dd.getInterpolationDuration()); + + final var DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_4.DISPLAY__DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID, 0); + } + + update(); + } + + @Override + public void delete() { + this.display = null; + } + + @Override + public void update() { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to update + } + + // location data + final var location = data.getLocation(); + if (location.getWorld() == null || !location.isWorldLoaded()) { + return; + } else { + display.setPosRaw(location.x(), location.y(), location.z()); + display.setYRot(location.getYaw()); + display.setXRot(location.getPitch()); + } + + if (display instanceof TextDisplay textDisplay && data instanceof TextHologramData textData) { + // line width + final var DATA_LINE_WIDTH_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_4.TEXT_DISPLAY__DATA_LINE_WIDTH_ID.getMapping()); + display.getEntityData().set((EntityDataAccessor) DATA_LINE_WIDTH_ID, Hologram.LINE_WIDTH); + + // background + final var DATA_BACKGROUND_COLOR_ID = ReflectionUtils.getStaticValue(TextDisplay.class, MappingKeys1_20_4.TEXT_DISPLAY__DATA_BACKGROUND_COLOR_ID.getMapping()); + + final var background = textData.getBackground(); + if (background == null) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND); + } else if (background == Hologram.TRANSPARENT) { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, 0); + } else { + display.getEntityData().set((EntityDataAccessor) DATA_BACKGROUND_COLOR_ID, background.asARGB()); + } + + // text shadow + if (textData.hasTextShadow()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SHADOW)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SHADOW)); + } + + // text alignment + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.LEFT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_LEFT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_LEFT)); + } + + // see through + if (textData.isSeeThrough()) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_SEE_THROUGH)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_SEE_THROUGH)); + } + + if (textData.getTextAlignment() == org.bukkit.entity.TextDisplay.TextAlignment.RIGHT) { + textDisplay.setFlags((byte) (textDisplay.getFlags() | TextDisplay.FLAG_ALIGN_RIGHT)); + } else { + textDisplay.setFlags((byte) (textDisplay.getFlags() & ~TextDisplay.FLAG_ALIGN_RIGHT)); + } + + } else if (display instanceof Display.ItemDisplay itemDisplay && data instanceof ItemHologramData itemData) { + // item + itemDisplay.setItemStack(ItemStack.fromBukkitCopy(itemData.getItemStack())); + + } else if (display instanceof Display.BlockDisplay blockDisplay && data instanceof BlockHologramData blockData) { + Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.of("minecraft:" + blockData.getBlock().name().toLowerCase(), ':')); + blockDisplay.setBlockState(block.defaultBlockState()); + } + + if (data instanceof DisplayHologramData displayData) { + // billboard data + display.setBillboardConstraints(switch (displayData.getBillboard()) { + case FIXED -> Display.BillboardConstraints.FIXED; + case VERTICAL -> Display.BillboardConstraints.VERTICAL; + case HORIZONTAL -> Display.BillboardConstraints.HORIZONTAL; + case CENTER -> Display.BillboardConstraints.CENTER; + }); + + // brightness + if (displayData.getBrightness() != null) { + display.setBrightnessOverride(new Brightness(displayData.getBrightness().getBlockLight(), displayData.getBrightness().getSkyLight())); + } + + // entity scale AND MORE! + display.setTransformation(new Transformation( + displayData.getTranslation(), + new Quaternionf(), + displayData.getScale(), + new Quaternionf()) + ); + + // entity shadow + display.setShadowRadius(displayData.getShadowRadius()); + display.setShadowStrength(displayData.getShadowStrength()); + + // view range + display.setViewRange(displayData.getVisibilityDistance()); + } + } + + + @Override + public boolean show(@NotNull final Player player) { + if (!new HologramShowEvent(this, player).callEvent()) { + return false; + } + + if (this.display == null) { + create(); // try to create it if it doesn't exist every time + } + + final var display = this.display; + if (display == null) { + return false; // could not be created, nothing to show + } + + if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + // TODO: cache player protocol version + // TODO: fix this +// final var protocolVersion = FancyHologramsPlugin.get().isUsingViaVersion() ? Via.getAPI().getPlayerVersion(player.getUniqueId()) : MINIMUM_PROTOCOL_VERSION; +// if (protocolVersion < MINIMUM_PROTOCOL_VERSION) { +// System.out.println("nope protocol"); +// return false; +// } + + serverPlayer.connection.send(new ClientboundAddEntityPacket(display)); + this.viewers.add(player.getUniqueId()); + refreshHologram(player); + + return true; + } + + @Override + public boolean hide(@NotNull final Player player) { + if (!new HologramHideEvent(this, player).callEvent()) { + return false; + } + + final var display = this.display; + if (display == null) { + return false; // doesn't exist, nothing to hide + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId())); + + this.viewers.remove(player.getUniqueId()); + return true; + } + + + @Override + public void refresh(@NotNull final Player player) { + final var display = this.display; + if (display == null) { + return; // doesn't exist, nothing to refresh + } + + if (!isViewer(player)) { + return; + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display)); + + if (display instanceof TextDisplay textDisplay) { + textDisplay.setText(PaperAdventure.asVanilla(getShownText(player))); + } + + final var values = new ArrayList>(); + + //noinspection unchecked + for (final var item : ((Int2ObjectMap>) getValue(display.getEntityData(), "e")).values()) { + values.add(item.value()); + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values)); + } + +} diff --git a/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_4.java b/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_4.java new file mode 100644 index 00000000..f9736289 --- /dev/null +++ b/plugins/fancyholograms-v2/implementation_1_20_4/src/main/java/de/oliver/fancyholograms/hologram/version/MappingKeys1_20_4.java @@ -0,0 +1,20 @@ +package de.oliver.fancyholograms.hologram.version; + +public enum MappingKeys1_20_4 { + + DISPLAY__DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID("r"), + DISPLAY__DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID("q"), + TEXT_DISPLAY__DATA_LINE_WIDTH_ID("aN"), + TEXT_DISPLAY__DATA_BACKGROUND_COLOR_ID("aO"), + ; + + private final String mapping; + + MappingKeys1_20_4(String mapping) { + this.mapping = mapping; + } + + public String getMapping() { + return mapping; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FHFeatureFlags.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FHFeatureFlags.java new file mode 100644 index 00000000..e5b4a05b --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FHFeatureFlags.java @@ -0,0 +1,16 @@ +package de.oliver.fancyholograms; + +import de.oliver.fancylib.featureFlags.FeatureFlag; +import de.oliver.fancylib.featureFlags.FeatureFlagConfig; + +public class FHFeatureFlags { + + public static final FeatureFlag DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS = new FeatureFlag("disable-holograms-for-bedrock-players", "Do not show holograms to bedrock players", false); + + public static void load() { + FeatureFlagConfig config = new FeatureFlagConfig(FancyHolograms.get()); + config.addFeatureFlag(DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS); + config.load(); + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHolograms.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHolograms.java new file mode 100644 index 00000000..541f7f53 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHolograms.java @@ -0,0 +1,387 @@ +package de.oliver.fancyholograms; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import de.oliver.fancyanalytics.api.FancyAnalyticsAPI; +import de.oliver.fancyanalytics.api.metrics.MetricSupplier; +import de.oliver.fancyanalytics.logger.ExtendedFancyLogger; +import de.oliver.fancyanalytics.logger.LogLevel; +import de.oliver.fancyanalytics.logger.appender.Appender; +import de.oliver.fancyanalytics.logger.appender.ConsoleAppender; +import de.oliver.fancyanalytics.logger.appender.JsonAppender; +import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.HologramConfiguration; +import de.oliver.fancyholograms.api.HologramManager; +import de.oliver.fancyholograms.api.HologramStorage; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.FancyHologramsCMD; +import de.oliver.fancyholograms.commands.FancyHologramsTestCMD; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.hologram.version.*; +import de.oliver.fancyholograms.listeners.BedrockPlayerListener; +import de.oliver.fancyholograms.listeners.NpcListener; +import de.oliver.fancyholograms.listeners.PlayerListener; +import de.oliver.fancyholograms.listeners.WorldListener; +import de.oliver.fancyholograms.storage.FlatFileHologramStorage; +import de.oliver.fancyholograms.storage.converter.FHConversionRegistry; +import de.oliver.fancyholograms.util.PluginUtils; +import de.oliver.fancylib.FancyLib; +import de.oliver.fancylib.Metrics; +import de.oliver.fancylib.VersionConfig; +import de.oliver.fancylib.serverSoftware.ServerSoftware; +import de.oliver.fancylib.versionFetcher.MasterVersionFetcher; +import de.oliver.fancylib.versionFetcher.VersionFetcher; +import de.oliver.fancysitula.api.IFancySitula; +import de.oliver.fancysitula.api.utils.ServerVersion; +import org.apache.maven.artifact.versioning.ComparableVersion; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +public final class FancyHolograms extends JavaPlugin implements FancyHologramsPlugin { + + private static @Nullable FancyHolograms INSTANCE; + private final ExtendedFancyLogger fancyLogger; + private final VersionFetcher versionFetcher = new MasterVersionFetcher("FancyHolograms"); + private final VersionConfig versionConfig = new VersionConfig(this, versionFetcher); + private final ScheduledExecutorService hologramThread = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setNameFormat("FancyHolograms-Holograms") + .build() + ); + private final ExecutorService fileStorageExecutor = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setDaemon(true) + .setPriority(Thread.MIN_PRIORITY + 1) + .setNameFormat("FancyHolograms-FileStorageExecutor") + .build() + ); + private FancyAnalyticsAPI fancyAnalytics; + private HologramConfiguration configuration = new FancyHologramsConfiguration(); + private HologramStorage hologramStorage = new FlatFileHologramStorage(); + private @Nullable HologramManagerImpl hologramsManager; + + public FancyHolograms() { + INSTANCE = this; + + Appender consoleAppender = new ConsoleAppender("[{loggerName}] ({threadName}) {logLevel}: {message}"); + String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date(System.currentTimeMillis())); + File logsFile = new File("plugins/FancyHolograms/logs/FH-logs-" + date + ".txt"); + if (!logsFile.exists()) { + try { + logsFile.getParentFile().mkdirs(); + logsFile.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + JsonAppender jsonAppender = new JsonAppender(false, false, true, logsFile.getPath()); + this.fancyLogger = new ExtendedFancyLogger("FancyHolograms", LogLevel.INFO, List.of(consoleAppender, jsonAppender), new ArrayList<>()); + } + + public static @NotNull FancyHolograms get() { + return Objects.requireNonNull(INSTANCE, "plugin is not initialized"); + } + + public static boolean canGet() { + return INSTANCE != null; + } + + @Override + public void onLoad() { + final var adapter = resolveHologramAdapter(); + + if (adapter == null) { + List supportedVersions = new ArrayList<>(List.of("1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4")); + supportedVersions.addAll(ServerVersion.getSupportedVersions()); + + fancyLogger.warn(""" + -------------------------------------------------- + Unsupported minecraft server version. + Please update the server to one of (%s). + Disabling the FancyHolograms plugin. + -------------------------------------------------- + """.formatted(String.join(" / ", supportedVersions))); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + hologramsManager = new HologramManagerImpl(this, adapter); + + fancyLogger.info("Successfully loaded FancyHolograms version %s".formatted(getDescription().getVersion())); + } + + @Override + public void onEnable() { + getHologramConfiguration().reload(this); // initialize configuration + + new FancyLib(INSTANCE); // initialize FancyLib + + if (!ServerSoftware.isPaper()) { + fancyLogger.warn(""" + -------------------------------------------------- + It is recommended to use Paper as server software. + Because you are not using paper, the plugin + might not work correctly. + -------------------------------------------------- + """); + } + + LogLevel logLevel; + try { + logLevel = LogLevel.valueOf(getHologramConfiguration().getLogLevel()); + } catch (IllegalArgumentException e) { + logLevel = LogLevel.INFO; + } + fancyLogger.setCurrentLevel(logLevel); + IFancySitula.LOGGER.setCurrentLevel(logLevel); + + FHFeatureFlags.load(); + + reloadCommands(); + + registerListeners(); + + versionConfig.load(); + if (!getHologramConfiguration().areVersionNotificationsMuted()) { + checkForNewerVersion(); + } + + registerMetrics(); + + getHologramsManager().initializeTasks(); + + if (getHologramConfiguration().isAutosaveEnabled()) { + getHologramThread().scheduleAtFixedRate(() -> { + if (hologramsManager != null) { + hologramsManager.saveHolograms(); + } + }, getHologramConfiguration().getAutosaveInterval(), getHologramConfiguration().getAutosaveInterval() * 60L, TimeUnit.SECONDS); + } + + FHConversionRegistry.registerBuiltInConverters(); + + fancyLogger.info("Successfully enabled FancyHolograms version %s".formatted(getDescription().getVersion())); + } + + @Override + public void onDisable() { + hologramsManager.saveHolograms(); + hologramThread.shutdown(); + fileStorageExecutor.shutdown(); + INSTANCE = null; + + fancyLogger.info("Successfully disabled FancyHolograms version %s".formatted(getDescription().getVersion())); + } + + @Override + public JavaPlugin getPlugin() { + return INSTANCE; + } + + @Override + public ExtendedFancyLogger getFancyLogger() { + return fancyLogger; + } + + public @NotNull VersionFetcher getVersionFetcher() { + return versionFetcher; + } + + public @NotNull VersionConfig getVersionConfig() { + return versionConfig; + } + + @ApiStatus.Internal + public @NotNull HologramManagerImpl getHologramsManager() { + return Objects.requireNonNull(this.hologramsManager, "plugin is not initialized"); + } + + @Override + public HologramManager getHologramManager() { + return Objects.requireNonNull(this.hologramsManager, "plugin is not initialized"); + } + + @Override + public HologramConfiguration getHologramConfiguration() { + return configuration; + } + + @Override + public void setHologramConfiguration(HologramConfiguration configuration, boolean reload) { + this.configuration = configuration; + + if (reload) { + configuration.reload(this); + reloadCommands(); + } + } + + @Override + public HologramStorage getHologramStorage() { + return hologramStorage; + } + + @Override + public void setHologramStorage(HologramStorage storage, boolean reload) { + this.hologramStorage = storage; + + if (reload) { + getHologramsManager().reloadHolograms(); + } + } + + public ScheduledExecutorService getHologramThread() { + return hologramThread; + } + + public ExecutorService getFileStorageExecutor() { + return this.fileStorageExecutor; + } + + private @Nullable Function resolveHologramAdapter() { + final var version = Bukkit.getMinecraftVersion(); + + // check if the server version is supported by FancySitula + if (ServerVersion.isVersionSupported(version)) { + return HologramImpl::new; + } + + return switch (version) { + case "1.20.3", "1.20.4" -> Hologram1_20_4::new; + case "1.20.2" -> Hologram1_20_2::new; + case "1.20", "1.20.1" -> Hologram1_20_1::new; + case "1.19.4" -> Hologram1_19_4::new; + default -> null; + }; + } + + public void reloadCommands() { + Collection commands = Arrays.asList(new HologramCMD(this), new FancyHologramsCMD(this)); + + if (getHologramConfiguration().isRegisterCommands()) { + commands.forEach(command -> getServer().getCommandMap().register("fancyholograms", command)); + } else { + commands.stream().filter(Command::isRegistered).forEach(command -> + command.unregister(getServer().getCommandMap())); + } + + if (false) { + FancyHologramsTestCMD fancyHologramsTestCMD = new FancyHologramsTestCMD(this); + getServer().getCommandMap().register("fancyholograms", fancyHologramsTestCMD); + } + } + + private void registerListeners() { + getServer().getPluginManager().registerEvents(new PlayerListener(this), this); + getServer().getPluginManager().registerEvents(new WorldListener(), this); + + if (PluginUtils.isFancyNpcsEnabled()) { + getServer().getPluginManager().registerEvents(new NpcListener(this), this); + } + + if (FHFeatureFlags.DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS.isEnabled() && PluginUtils.isFloodgateEnabled()) { + getServer().getPluginManager().registerEvents(new BedrockPlayerListener(), this); + } + } + + private void checkForNewerVersion() { + final var current = new ComparableVersion(versionConfig.getVersion()); + + supplyAsync(getVersionFetcher()::fetchNewestVersion).thenApply(Objects::requireNonNull).whenComplete((newest, error) -> { + if (error != null || newest.compareTo(current) <= 0) { + return; // could not get the newest version or already on latest + } + + fancyLogger.warn(""" + + ------------------------------------------------------- + You are not using the latest version of the FancyHolograms plugin. + Please update to the newest version (%s). + %s + ------------------------------------------------------- + """.formatted(newest, getVersionFetcher().getDownloadUrl())); + }); + } + + private void registerMetrics() { + boolean isDevelopmentBuild = !versionConfig.getBuild().equalsIgnoreCase("undefined"); + + Metrics metrics = new Metrics(this, 17990); + metrics.addCustomChart(new Metrics.SingleLineChart("total_holograms", () -> hologramsManager.getHolograms().size())); + metrics.addCustomChart(new Metrics.SimplePie("update_notifications", () -> configuration.areVersionNotificationsMuted() ? "No" : "Yes")); + metrics.addCustomChart(new Metrics.SimplePie("using_development_build", () -> isDevelopmentBuild ? "Yes" : "No")); + + fancyAnalytics = new FancyAnalyticsAPI("3b77bd59-2b01-46f2-b3aa-a9584401797f", "E2gW5zc2ZTk1OGFkNGY2ZDQ0ODlM6San"); + fancyAnalytics.getConfig().setDisableLogging(true); + + if (!isDevelopmentBuild) { + return; + } + + fancyAnalytics.registerMinecraftPluginMetrics(INSTANCE); + fancyAnalytics.getExceptionHandler().registerLogger(getLogger()); + fancyAnalytics.getExceptionHandler().registerLogger(Bukkit.getLogger()); + fancyAnalytics.getExceptionHandler().registerLogger(fancyLogger); + + fancyAnalytics.registerStringMetric(new MetricSupplier<>("commit_hash", () -> versionConfig.getHash().substring(0, 7))); + + fancyAnalytics.registerStringMetric(new MetricSupplier<>("server_size", () -> { + long onlinePlayers = Bukkit.getOnlinePlayers().size(); + + if (onlinePlayers == 0) { + return "empty"; + } + + if (onlinePlayers <= 25) { + return "small"; + } + + if (onlinePlayers <= 100) { + return "medium"; + } + + if (onlinePlayers <= 500) { + return "large"; + } + + return "very_large"; + })); + + fancyAnalytics.registerNumberMetric(new MetricSupplier<>("amount_holograms", () -> (double) hologramsManager.getHolograms().size())); + fancyAnalytics.registerStringMetric(new MetricSupplier<>("enabled_update_notifications", () -> configuration.areVersionNotificationsMuted() ? "false" : "true")); + fancyAnalytics.registerStringMetric(new MetricSupplier<>("fflag_disable_holograms_for_bedrock_players", () -> FHFeatureFlags.DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS.isEnabled() ? "true" : "false")); + fancyAnalytics.registerStringMetric(new MetricSupplier<>("using_development_build", () -> isDevelopmentBuild ? "true" : "false")); + + fancyAnalytics.registerStringArrayMetric(new MetricSupplier<>("hologram_type", () -> { + if (hologramsManager == null) { + return new String[0]; + } + + return hologramsManager.getHolograms().stream() + .map(h -> h.getData().getType().name()) + .toArray(String[]::new); + })); + + + fancyAnalytics.initialize(); + } + + public FancyAnalyticsAPI getFancyAnalytics() { + return fancyAnalytics; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHologramsConfiguration.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHologramsConfiguration.java new file mode 100644 index 00000000..aae64303 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/FancyHologramsConfiguration.java @@ -0,0 +1,139 @@ +package de.oliver.fancyholograms; + +import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.HologramConfiguration; +import de.oliver.fancylib.ConfigHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * The FancyHologramsConfig class is responsible for managing the configuration of the FancyHolograms plugin. + * It handles loading and saving hologram data, as well as providing access to various configuration settings. + */ +public final class FancyHologramsConfiguration implements HologramConfiguration { + + /** + * Indicates whether version notifications are muted. + */ + private boolean versionNotifsMuted; + + /** + * Indicates whether autosave is enabled. + */ + private boolean autosaveEnabled; + + /** + * The interval at which autosave is performed. + */ + private int autosaveInterval; + + /** + * Indicates whether the plugin should save holograms when they are changed. + */ + private boolean saveOnChangedEnabled; + + /** + * The default visibility distance for holograms. + */ + private int defaultVisibilityDistance; + + /** + * Indicates whether commands should be registered. + *

+ * This is useful for users who want to use the plugin's API only. + */ + private boolean registerCommands; + + /** + * The log level for the plugin. + */ + private String logLevel; + + /** + * The interval at which hologram visibility is updated. + */ + private int updateVisibilityInterval; + + @Override + public void reload(@NotNull FancyHologramsPlugin plugin) { + FancyHolograms pluginImpl = (FancyHolograms) plugin; + pluginImpl.reloadConfig(); + + final var config = pluginImpl.getConfig(); + + versionNotifsMuted = (boolean) ConfigHelper.getOrDefault(config, "mute_version_notification", false); + config.setInlineComments("mute_version_notification", List.of("Whether version notifications are muted.")); + + autosaveEnabled = (boolean) ConfigHelper.getOrDefault(config, "enable_autosave", true); + config.setInlineComments("enable_autosave", List.of("Whether autosave is enabled.")); + + autosaveInterval = (int) ConfigHelper.getOrDefault(config, "autosave_interval", 15); + config.setInlineComments("autosave_interval", List.of("The interval at which autosave is performed in minutes.")); + + saveOnChangedEnabled = (boolean) ConfigHelper.getOrDefault(config, "save_on_changed", true); + config.setInlineComments("save_on_changed", List.of("Whether the plugin should save holograms when they are changed.")); + + defaultVisibilityDistance = (int) ConfigHelper.getOrDefault(config, "visibility_distance", 20); + config.setInlineComments("visibility_distance", List.of("The default visibility distance for holograms.")); + + registerCommands = (boolean) ConfigHelper.getOrDefault(config, "register_commands", true); + config.setInlineComments("register_commands", List.of("Whether the plugin should register its commands.")); + + config.set("report_errors_to_sentry", null); + config.setInlineComments("report_errors_to_sentry", null); + + config.setInlineComments("log_level", List.of("The log level for the plugin (DEBUG, INFO, WARN, ERROR).")); + logLevel = (String) ConfigHelper.getOrDefault(config, "log_level", "INFO"); + + updateVisibilityInterval = (int) ConfigHelper.getOrDefault(config, "update_visibility_interval", 20); + config.setInlineComments("update_visibility_interval", List.of("The interval at which hologram visibility is updated in ticks.")); + + if (pluginImpl.isEnabled()) { + plugin.getHologramThread().submit(pluginImpl::saveConfig); + } else { + // Can't dispatch task if plugin is disabled + pluginImpl.saveConfig(); + } + } + + @Override + public boolean areVersionNotificationsMuted() { + return versionNotifsMuted; + } + + @Override + public boolean isAutosaveEnabled() { + return autosaveEnabled; + } + + @Override + public int getAutosaveInterval() { + return autosaveInterval; + } + + @Override + public boolean isSaveOnChangedEnabled() { + return saveOnChangedEnabled; + } + + @Override + public int getDefaultVisibilityDistance() { + return defaultVisibilityDistance; + } + + @Override + public boolean isRegisterCommands() { + return registerCommands; + } + + @Override + public String getLogLevel() { + return logLevel; + } + + @Override + public int getUpdateVisibilityInterval() { + return updateVisibilityInterval; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/HologramManagerImpl.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/HologramManagerImpl.java new file mode 100644 index 00000000..8db467c3 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/HologramManagerImpl.java @@ -0,0 +1,313 @@ +package de.oliver.fancyholograms; + +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import de.oliver.fancyholograms.api.HologramManager; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramsLoadedEvent; +import de.oliver.fancyholograms.api.events.HologramsUnloadedEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancynpcs.api.FancyNpcsPlugin; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; +import org.joml.Vector3f; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * The FancyHologramsManager class is responsible for managing holograms in the FancyHolograms plugin. + * It provides methods for adding, removing, and retrieving holograms, as well as other related operations. + */ +public final class HologramManagerImpl implements HologramManager { + + private final @NotNull FancyHolograms plugin; + /** + * The adapter function used to create holograms from hologram data. + */ + private final @NotNull Function adapter; + /** + * A map of hologram names to their corresponding hologram instances. + */ + private final Map holograms = new ConcurrentHashMap<>(); + /** + * Whether holograms are loaded or not + */ + private boolean isLoaded = false; + + HologramManagerImpl(@NotNull final FancyHolograms plugin, @NotNull final Function adapter) { + this.plugin = plugin; + this.adapter = adapter; + } + + /** + * @return A read-only collection of loaded holograms. + */ + @Override + public @NotNull + @UnmodifiableView Collection getHolograms() { + return Collections.unmodifiableCollection(this.holograms.values()); + } + + /** + * Returns a read-only view of the currently loaded persistent holograms. + * + * @return A read-only collection of holograms. + */ + @Override + public @NotNull + @UnmodifiableView Collection getPersistentHolograms() { + return this.holograms.values().stream().filter(hologram -> hologram.getData().isPersistent()).toList(); + } + + + /** + * Finds a hologram by name. + * + * @param name The name of the hologram to lookup. + * @return An optional containing the found hologram, or empty if not found. + */ + public @NotNull Optional getHologram(@NotNull final String name) { + return Optional.ofNullable(this.holograms.get(name.toLowerCase(Locale.ROOT))); + } + + /** + * Adds a hologram to this manager. + * + * @param hologram The hologram to add. + */ + public void addHologram(@NotNull final Hologram hologram) { + this.holograms.put(hologram.getData().getName().toLowerCase(Locale.ROOT), hologram); + } + + /** + * Removes a hologram from this manager. + * + * @param hologram The hologram to remove. + */ + public void removeHologram(@NotNull final Hologram hologram) { + removeHologram(hologram.getData().getName()); + } + + /** + * Removes a hologram from this manager by name. + * + * @param name The name of the hologram to remove. + * @return An optional containing the removed hologram, or empty if not found. + */ + public @NotNull Optional removeHologram(@NotNull final String name) { + Optional optionalHologram = Optional.ofNullable(this.holograms.remove(name.toLowerCase(Locale.ROOT))); + + optionalHologram.ifPresent(hologram -> { + for (UUID viewer : hologram.getViewers()) { + Player player = Bukkit.getPlayer(viewer); + if (player != null) { + FancyHolograms.get().getHologramThread().submit(() -> hologram.forceHideHologram(player)); + } + } + + FancyHolograms.get().getHologramThread().submit(() -> plugin.getHologramStorage().delete(hologram)); + } + ); + + return optionalHologram; + } + + /** + * Creates a new hologram with the specified hologram data. + * + * @param data The hologram data for the new hologram. + * @return The created hologram. + */ + public @NotNull Hologram create(@NotNull final HologramData data) { + Hologram hologram = this.adapter.apply(data); + hologram.createHologram(); + return hologram; + } + + public void saveHolograms() { + if (!isLoaded) { + return; + } + + plugin.getHologramStorage().saveBatch(getPersistentHolograms(), false); + } + + @Override + public void loadHolograms() { + List allLoaded = new ArrayList<>(); + + for (World world : Bukkit.getWorlds()) { + Collection loaded = plugin.getHologramStorage().loadAll(world.getName()); + loaded.forEach(this::addHologram); + + allLoaded.addAll(loaded); + } + isLoaded = true; + + FancyHolograms.get().getHologramThread().submit(() -> Bukkit.getPluginManager().callEvent(new HologramsLoadedEvent(ImmutableList.copyOf(allLoaded)))); + + FancyHolograms.get().getFancyLogger().info(String.format("Loaded %d holograms for all loaded worlds", allLoaded.size())); + } + + public void loadHolograms(String world) { + ImmutableList loaded = ImmutableList.copyOf(plugin.getHologramStorage().loadAll(world)); + loaded.forEach(this::addHologram); + + isLoaded = true; + + Bukkit.getPluginManager().callEvent(new HologramsLoadedEvent(ImmutableList.copyOf(loaded))); + + FancyHolograms.get().getFancyLogger().info(String.format("Loaded %d holograms for world %s", loaded.size(), world)); + } + + /** + * Initializes tasks for managing holograms, such as loading and refreshing them. + *

+ * This method is intended to be called internally by the plugin. + */ + void initializeTasks() { + ScheduledExecutorService hologramThread = plugin.getHologramThread(); + hologramThread.submit(() -> { + loadHolograms(); + + hologramThread.scheduleAtFixedRate(() -> { + for (final Hologram hologram : this.plugin.getHologramsManager().getHolograms()) { + for (final Player player : Bukkit.getOnlinePlayers()) { + hologram.forceUpdateShownStateFor(player); + } + } + }, 0, plugin.getHologramConfiguration().getUpdateVisibilityInterval() * 50, TimeUnit.MILLISECONDS); + }); + + final var updateTimes = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofMinutes(5)) + .build(); + + hologramThread.scheduleAtFixedRate(() -> { + final var time = System.currentTimeMillis(); + + for (final var hologram : getHolograms()) { + HologramData data = hologram.getData(); + if (data.hasChanges()) { + hologram.forceUpdate(); + hologram.refreshForViewersInWorld(); + data.setHasChanges(false); + + if (data instanceof TextHologramData) { + updateTimes.put(hologram.getData().getName(), time); + } + } + } + }, 50, 1000, TimeUnit.MILLISECONDS); + + hologramThread.scheduleWithFixedDelay(() -> { + final var time = System.currentTimeMillis(); + + for (final var hologram : getHolograms()) { + if (hologram.getData() instanceof TextHologramData textData) { + final var interval = textData.getTextUpdateInterval(); + if (interval < 1) { + continue; // doesn't update + } + + final var lastUpdate = updateTimes.asMap().get(textData.getName()); + if (lastUpdate != null && time < (lastUpdate + interval)) { + continue; + } + + if (lastUpdate == null || time > (lastUpdate + interval)) { + hologram.refreshForViewersInWorld(); + updateTimes.put(textData.getName(), time); + } + } + } + }, 50, 50, TimeUnit.MILLISECONDS); + } + + /** + * Reloads holograms by clearing the existing holograms and loading them again from the plugin's configuration. + */ + public void reloadHolograms() { + unloadHolograms(); + loadHolograms(); + } + + public void unloadHolograms() { + FancyHolograms.get().getHologramThread().submit(() -> { + List unloaded = new ArrayList<>(); + + for (final var hologram : this.getPersistentHolograms()) { + this.holograms.remove(hologram.getName()); + unloaded.add(hologram); + + for (UUID viewer : hologram.getViewers()) { + Player player = Bukkit.getPlayer(viewer); + if (player != null) { + hologram.forceHideHologram(player); + } + } + } + + Bukkit.getPluginManager().callEvent(new HologramsUnloadedEvent(ImmutableList.copyOf(unloaded))); + }); + } + + public void unloadHolograms(String world) { + final var online = List.copyOf(Bukkit.getOnlinePlayers()); + + FancyHolograms.get().getHologramThread().submit(() -> { + List h = getPersistentHolograms().stream() + .filter(hologram -> hologram.getData().getLocation().getWorld().getName().equals(world)) + .toList(); + + FancyHolograms.get().getHologramStorage().saveBatch(h, false); + + for (final Hologram hologram : h) { + this.holograms.remove(hologram.getName()); + online.forEach(hologram::forceHideHologram); + } + + Bukkit.getPluginManager().callEvent(new HologramsUnloadedEvent(ImmutableList.copyOf(h))); + }); + } + + /** + * Syncs a hologram with its linked NPC, if any. + * + * @param hologram The hologram to sync. + */ + public void syncHologramWithNpc(@NotNull final Hologram hologram) { + final var linkedNpcName = hologram.getData().getLinkedNpcName(); + if (linkedNpcName == null) { + return; + } + + final var npc = FancyNpcsPlugin.get().getNpcManager().getNpc(linkedNpcName); + if (npc == null) { + return; + } + + npc.getData().setDisplayName(""); + npc.getData().setShowInTab(false); + npc.updateForAll(); + + final var npcScale = npc.getData().getScale(); + + if (hologram.getData() instanceof DisplayHologramData displayData) { + displayData.setScale(new Vector3f(npcScale)); + } + + final var location = npc.getData().getLocation().clone().add(0, (npc.getEyeHeight() * npcScale) + (0.5 * npcScale), 0); + hologram.getData().setLocation(location); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsCMD.java new file mode 100644 index 00000000..13648770 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsCMD.java @@ -0,0 +1,139 @@ +package de.oliver.fancyholograms.commands; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.storage.converter.ConverterTarget; +import de.oliver.fancyholograms.storage.converter.FHConversionRegistry; +import de.oliver.fancyholograms.storage.converter.HologramConversionSession; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancylib.translations.message.Message; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public final class FancyHologramsCMD extends Command { + + @NotNull + private final FancyHolograms plugin; + + public FancyHologramsCMD(@NotNull final FancyHolograms plugin) { + super("fancyholograms"); + setPermission("fancyholograms.admin"); + this.plugin = plugin; + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!testPermission(sender)) { + return false; + } + + if (args.length < 1) { + MessageHelper.info(sender, Constants.FH_COMMAND_USAGE); + return false; + } + + switch (args[0].toLowerCase(Locale.ROOT)) { + case "save" -> { + this.plugin.getHologramsManager().saveHolograms(); + MessageHelper.success(sender, "Saved all holograms"); + } + case "reload" -> { + this.plugin.getHologramConfiguration().reload(plugin); + this.plugin.getHologramsManager().reloadHolograms(); + this.plugin.reloadCommands(); + + MessageHelper.success(sender, "Reloaded config and holograms"); + } + case "version" -> { + FancyHolograms.get().getHologramThread().submit(() -> { + FancyHolograms.get().getVersionConfig().checkVersionAndDisplay(sender, false); + }); + } + case "convert" -> { + if (args.length < 3) { + MessageHelper.info(sender, "Usage: /fancyholograms convert [args...]"); + return false; + } + + final String converterId = args[1]; + FHConversionRegistry.getConverterById(converterId) + .ifPresentOrElse((converter) -> { + final String[] converterArgs = Arrays.asList(args) + .subList(2, args.length) + .toArray(String[]::new); + + final ConverterTarget target = ConverterTarget.ofStringNullable(args[2]); + + if (target == null) { + MessageHelper.error(sender, "Invalid regex for your conversion target!"); + return; + } + + final HologramConversionSession session = new HologramConversionSession(target, sender, converterArgs); + + try { + final List holograms = converter.convert(session); + + for (final HologramData data : holograms) { + final Hologram hologram = this.plugin.getHologramsManager().create(data); + this.plugin.getHologramsManager().addHologram(hologram); + } + + this.plugin.getHologramsManager().saveHolograms(); + // TODO(matt): Give options to delete them or teleport and a list of IDs please + + MessageHelper.success(sender, String.format("Converted successfully, produced %s total holograms!", holograms.size())); + } catch (Exception error) { + MessageHelper.error(sender, error.getMessage()); + } + }, () -> MessageHelper.error(sender, "That converter is not registered. Look at the developer documentation if you are adding converters.")); + } + default -> { + MessageHelper.info(sender, Constants.FH_COMMAND_USAGE); + return false; + } + } + + return true; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) throws IllegalArgumentException { + if (args.length < 1) { + return Collections.emptyList(); + } + + List suggestions = new ArrayList<>(); + + if (args.length == 1) { + suggestions.addAll(Arrays.asList("version", "reload", "save", "convert")); + } else { + if (Objects.equals(args[0], "convert")) { + + if (args.length == 2) { + suggestions.addAll(FHConversionRegistry.getAllUsableConverterIds()); + } else if (args.length == 3) { + final String converterId = args[1]; + FHConversionRegistry.getConverterById(converterId) + .ifPresent((converter) -> { + suggestions.addAll(converter.getConvertableHolograms()); + suggestions.add("*"); + }); + } + } + } + + String lastArgument = args[args.length - 1]; + + return suggestions.stream() + .filter(alias -> alias.startsWith(lastArgument.toLowerCase(Locale.ROOT))) + .toList(); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsTestCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsTestCMD.java new file mode 100644 index 00000000..12dbc22f --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/FancyHologramsTestCMD.java @@ -0,0 +1,103 @@ +package de.oliver.fancyholograms.commands; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Color; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class FancyHologramsTestCMD extends Command { + + @NotNull + private final FancyHolograms plugin; + + public FancyHologramsTestCMD(@NotNull final FancyHolograms plugin) { + super("FancyHologramsTest"); + setPermission("fancyholograms.admin"); + this.plugin = plugin; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + if (args.length == 1) { + return Arrays.asList("spawn100", "test1"); + } + + return Collections.emptyList(); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!testPermission(sender)) { + return false; + } + + if (!(sender instanceof Player p)) { + MessageHelper.error(sender, "Only players can use this command!"); + return false; + } + + if (args.length == 1 && args[0].equalsIgnoreCase("spawn100")) { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + int n = (i * 10 + j) + 1; + TextHologramData textData = new TextHologramData("holo-" + n, p.getLocation().clone().add(5 * i + 1, 0, 5 * j + 1)); + textData.setText(Arrays.asList( + "This is a test hologram! (#" + n + ")", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris." + )); + textData.setTextUpdateInterval(100) + .setScale(new Vector3f(.5f, .5f, .5f)) + .setVisibilityDistance(100); + + Hologram hologram = this.plugin.getHologramsManager().create(textData); + hologram.createHologram(); + hologram.updateShownStateFor(p); + } + } + + return true; + } else if (args.length == 1 && args[0].equalsIgnoreCase("test1")) { + TextHologramData textData = new TextHologramData("holo-test1", p.getLocation()); + textData.setText(Arrays.asList( + "This is a test hologram!", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.", + "Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris." + )) + .setTextUpdateInterval(100) + .setTextAlignment(TextDisplay.TextAlignment.CENTER) + .setBackground(Color.fromARGB(15, 78, 237, 176)) + .setTextShadow(true) + .setScale(new Vector3f(2, 2, 2)) + .setBillboard(Display.Billboard.CENTER) + .setBrightness(new Display.Brightness(15, 15)) + .setShadowRadius(3) + .setShadowStrength(3) + .setVisibilityDistance(100); + + Hologram hologram = this.plugin.getHologramsManager().create(textData); + hologram.createHologram(); + hologram.updateShownStateFor(p); + } + + return false; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java new file mode 100644 index 00000000..6e0821a3 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java @@ -0,0 +1,350 @@ +package de.oliver.fancyholograms.commands; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.hologram.HologramType; +import de.oliver.fancyholograms.commands.hologram.*; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancyholograms.util.PluginUtils; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancynpcs.api.FancyNpcsPlugin; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public final class HologramCMD extends Command { + + @NotNull + private final FancyHolograms plugin; + + public HologramCMD(@NotNull final FancyHolograms plugin) { + super("hologram", "Main command for the FancyHolograms plugin", "/hologram help", List.of("holograms", "holo", "fholo")); + + setPermission("fancyholograms.admin"); + + this.plugin = plugin; + } + + public static boolean callModificationEvent(@NotNull final Hologram hologram, @NotNull final CommandSender player, @NotNull final HologramData updatedData, @NotNull final HologramUpdateEvent.HologramModification modification) { + final var result = new HologramUpdateEvent(hologram, player, updatedData, modification).callEvent(); + + if (!result) { + MessageHelper.error(player, "Cancelled hologram modification"); + } + + return result; + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (!testPermission(sender)) { + return false; + } + + if (args.length == 0 || args[0].equalsIgnoreCase("help")) { + MessageHelper.info(sender, Constants.HELP_TEXT + (!PluginUtils.isFancyNpcsEnabled() ? "" : "\n" + Constants.HELP_TEXT_NPCS)); + return false; + } + + + if (args[0].equalsIgnoreCase("list")) { + return new ListCMD().run(sender, null, args); + } + + + if (args.length < 2) { + MessageHelper.error(sender, "Wrong usage: /hologram help"); + return false; + } + + + if (args[0].equalsIgnoreCase("create")) { + return new CreateCMD().run(sender, null, args); + } + + if (args[0].equalsIgnoreCase("nearby")) { + return new NearbyCMD().run(sender, null, args); + } + + final var hologram = this.plugin.getHologramsManager().getHologram(args[1]).orElse(null); + if (hologram == null) { + MessageHelper.error(sender, "Could not find hologram: '" + args[1] + "'"); + return false; + } + + + return switch (args[0].toLowerCase(Locale.ROOT)) { + case "info" -> new InfoCMD().run(sender, hologram, args); + case "remove" -> new RemoveCMD().run(sender, hologram, args); + case "teleport" -> new TeleportCMD().run(sender, hologram, args); + case "copy" -> new CopyCMD().run(sender, hologram, args); + case "edit" -> { + if (args.length < 3) { + MessageHelper.error(sender, "Wrong usage: /hologram help"); + yield false; + } + + final var updated = edit(sender, hologram, args); + + if (updated) { + if (sender instanceof Player p) { + hologram.forceUpdate(); + hologram.refreshHologram(p); + } + hologram.queueUpdate(); + } + + yield updated; + } + default -> false; + }; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) throws IllegalArgumentException { + if (args.length == 0) { + return Collections.emptyList(); + } + + // /holo {tab:action} + if (args.length == 1) { + return Stream.of("help", "list", "teleport", "create", "remove", "edit", "copy", "info", "nearby").filter(input -> input.startsWith(args[0].toLowerCase(Locale.ROOT))).toList(); + } + + // /holo create {tab:type} + if (args.length == 2 && args[0].equalsIgnoreCase("create")) { + return Arrays.asList("text", "item", "block"); + } + + // /holo [action] {tab:hologram} + if (args.length == 2) { + final var action = args[0].toLowerCase(Locale.ROOT); + + if (!Set.of("teleport", "remove", "edit", "copy", "info").contains(action)) { + return Collections.emptyList(); + } + + return this.plugin.getHologramsManager().getPersistentHolograms().stream().map(hologram -> hologram.getData().getName()).filter(input -> input.toLowerCase().startsWith(args[1].toLowerCase(Locale.ROOT))).toList(); + } + + final var hologram = this.plugin.getHologramsManager().getHologram(args[1]).orElse(null); + if (hologram == null) { + return Collections.emptyList(); + } + + HologramType type = hologram.getData().getType(); + + // /holo edit [hologram] {tab:option} + if (args.length == 3) { + if (!args[0].equalsIgnoreCase("edit")) { + return Collections.emptyList(); + } + + final var usingNpcs = PluginUtils.isFancyNpcsEnabled(); + + List suggestions = new ArrayList<>(Arrays.asList("position", "moveHere", "center", "moveTo", "rotate", "rotatepitch", "billboard", "scale", "translate", "visibilityDistance", "visibility", "shadowRadius", "shadowStrength", "brightness", usingNpcs ? "linkWithNpc" : "", usingNpcs ? "unlinkWithNpc" : "")); + suggestions.addAll(type.getCommands()); + + return suggestions.stream().filter(input -> input.toLowerCase().startsWith(args[2].toLowerCase(Locale.ROOT))).toList(); + } + + if (!args[0].equalsIgnoreCase("edit")) { + return Collections.emptyList(); + } + + // /holo edit [hologram] [option] {tab:contextual} + if (args.length == 4) { + final var suggestions = switch (args[2].toLowerCase(Locale.ROOT)) { + case "billboard" -> { + final var values = new ArrayList<>(List.of(Display.Billboard.values())); + + if (hologram.getData() instanceof DisplayHologramData displayData) { + values.remove(displayData.getBillboard()); + } + + yield values.stream().map(Enum::name); + } + case "background" -> { + TextHologramData textData = (TextHologramData) hologram.getData(); + final var colors = new ArrayList<>(NamedTextColor.NAMES.keys()); + + colors.add("reset"); + colors.add("default"); + colors.add("transparent"); + + final var current = textData.getBackground(); + + if (current == null) { + colors.remove("reset"); + colors.remove("default"); + } else if (current == Hologram.TRANSPARENT) { + colors.remove("transparent"); + } else { + NamedTextColor named = current.getAlpha() == 255 ? NamedTextColor.namedColor(current.asRGB()) : null; + colors.add(named != null ? named.toString() : '#' + Integer.toHexString(current.asARGB())); + } + + yield colors.stream(); + } + case "textshadow" -> { + TextHologramData textData = (TextHologramData) hologram.getData(); + yield Stream.of(!textData.hasTextShadow()).map(Object::toString); + } + case "brightness" -> Stream.of("block", "sky"); + case "textalignment" -> Arrays.stream(TextDisplay.TextAlignment.values()).map(Enum::name); + case "setline", "removeline" -> { + TextHologramData textData = (TextHologramData) hologram.getData(); + yield IntStream.range(1, textData.getText().size() + 1).mapToObj(Integer::toString); + } + case "linkwithnpc" -> { + if (!PluginUtils.isFancyNpcsEnabled()) { + yield Stream.empty(); + } + + yield FancyNpcsPlugin.get().getNpcManager().getAllNpcs().stream().map(npc -> npc.getData().getName()); + } + case "block" -> Arrays.stream(Material.values()).filter(Material::isBlock).map(Enum::name); + case "seethrough" -> Stream.of("true", "false"); + case "visibility" -> new VisibilityCMD().tabcompletion(sender, hologram, args).stream(); + + default -> null; + }; + + if (suggestions != null) { + return suggestions.filter(input -> input.toLowerCase().startsWith(args[3].toLowerCase(Locale.ROOT))).toList(); + } + } + + // /holo edit [hologram] setline [number] {tab:line_text} + if (args[2].equalsIgnoreCase("setline")) { + TextHologramData textData = (TextHologramData) hologram.getData(); + + final var index = Ints.tryParse(args[3]); + if (index == null || index < 1 || index > textData.getText().size()) { + return Collections.emptyList(); + } + + return List.of(textData.getText().get(index - 1)); + } + + // /holo edit [hologram] moveto {tab:x} {tab:y} {tab:z} + if (args[2].equalsIgnoreCase("moveto")) { + if (!(sender instanceof Player player)) { + return Collections.emptyList(); + } + + final var suggestions = new ArrayList(); + suggestions.add("~"); + suggestions.add("~~"); + + if (args.length == 7) { + suggestions.add(String.valueOf(player.getLocation().getYaw())); + } + + if (args.length == 8) { + suggestions.add(String.valueOf(player.getLocation().getPitch())); + } + + final var target = player.getTargetBlockExact(10); + if (target != null) { + final var coordinate = switch (args.length) { + case 4 -> target.getX(); + case 5 -> target.getY(); + case 6 -> target.getZ(); + default -> null; + }; + + suggestions.add(String.valueOf(coordinate)); + } + + return suggestions; + } + + if(args[2].equalsIgnoreCase("brightness")) { + if(args.length == 4) { + return List.of("block", "sky"); + } + + if(args.length > 5) { + return Collections.emptyList(); + } + + return List.of("0", "5", "10", "15"); + } + + return Collections.emptyList(); + } + + private boolean edit(@NotNull final CommandSender player, @NotNull final Hologram hologram, @NotNull final String[] args) { + final var action = args[2].toLowerCase(); + + // actions without a data + switch (action) { + case "position", "movehere" -> { + return new MoveHereCMD().run(player, hologram, args); + } + case "center" -> { + return new CenterCMD().run(player, hologram, args); + } + case "unlinkwithnpc" -> { + return new UnlinkWithNpcCMD().run(player, hologram, args); + } + case "item" -> { + return new ItemCMD().run(player, hologram, args); + } + } + + if (args.length == 3) { + MessageHelper.error(player, "Wrong usage: /hologram help"); + return false; + } + + return switch (action) { + // display data + case "moveto" -> new MoveToCMD().run(player, hologram, args); + case "rotate" -> new RotateCMD().run(player, hologram, args); + case "rotatepitch" -> new RotatePitchCMD().run(player, hologram, args); + case "billboard" -> new BillboardCMD().run(player, hologram, args); + case "scale" -> new ScaleCMD().run(player, hologram, args); + case "translate" -> new TranslateCommand().run(player, hologram, args); + case "updatetextinterval" -> new UpdateTextIntervalCMD().run(player, hologram, args); + case "visibilitydistance" -> new VisibilityDistanceCMD().run(player, hologram, args); + case "visibility" -> new VisibilityCMD().run(player, hologram, args); + case "linkwithnpc" -> new LinkWithNpcCMD().run(player, hologram, args); + case "shadowradius" -> new ShadowRadiusCMD().run(player, hologram, args); + case "shadowstrength" -> new ShadowStrengthCMD().run(player, hologram, args); + case "brightness" -> new BrightnessCMD().run(player, hologram, args); + + // text data + case "background" -> new BackgroundCMD().run(player, hologram, args); + case "addline" -> new AddLineCMD().run(player, hologram, args); + case "setline" -> new SetLineCMD().run(player, hologram, args); + case "removeline" -> new RemoveLineCMD().run(player, hologram, args); + case "insertbefore" -> new InsertBeforeCMD().run(player, hologram, args); + case "insertafter" -> new InsertAfterCMD().run(player, hologram, args); + case "textshadow" -> new TextShadowCMD().run(player, hologram, args); + case "textalignment" -> new TextAlignmentCMD().run(player, hologram, args); + case "seethrough" -> new SeeThroughCMD().run(player, hologram, args); + + // block data + case "block" -> new BlockCMD().run(player, hologram, args); + + default -> false; + }; + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/Subcommand.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/Subcommand.java new file mode 100644 index 00000000..edd4985d --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/Subcommand.java @@ -0,0 +1,16 @@ +package de.oliver.fancyholograms.commands; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface Subcommand { + + List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args); + + boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args); + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/AddLineCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/AddLineCMD.java new file mode 100644 index 00000000..4563b8d6 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/AddLineCMD.java @@ -0,0 +1,40 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AddLineCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + if (!(player.hasPermission("fancyholograms.hologram.edit.line.add"))) { + MessageHelper.error(player, "You don't have the required permission to add a line to this hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + String text = ""; + for (int i = 3; i < args.length; i++) { + text += args[i] + " "; + } + text = text.substring(0, text.length() - 1); + + return SetLineCMD.setLine(player, hologram, Integer.MAX_VALUE, text); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BackgroundCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BackgroundCMD.java new file mode 100644 index 00000000..1c3a9f5f --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BackgroundCMD.java @@ -0,0 +1,91 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Color; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +public class BackgroundCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + if (!(player.hasPermission("fancyholograms.hologram.edit.background"))) { + MessageHelper.error(player, "You don't have the required permission to chnage the background of a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + + final var color = args[3].toLowerCase(Locale.ROOT); + + final Color background; + + if (color.equals("reset") || color.equals("default")) { + background = null; + } else { + if (color.equals("transparent")) { + background = Hologram.TRANSPARENT; + } else if (color.startsWith("#")) { + Color parsed = Color.fromARGB((int) Long.parseLong(color.substring(1), 16)); + //make background solid color if RGB hex provided + if (color.length() == 7) background = parsed.setAlpha(255); + else background = parsed; + } else { + NamedTextColor named = NamedTextColor.NAMES.value(color.replace(' ', '_')); + background = named == null ? null : Color.fromARGB(named.value() | 0xC8000000); + } + + if (background == null) { + MessageHelper.error(player, "Could not parse background color"); + return false; + } + } + + if (Objects.equals(background, textData.getBackground())) { + MessageHelper.warning(player, "This hologram already has this background color"); + return false; + } + + final var copied = textData.copy(textData.getName()); + copied.setBackground(background); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.BACKGROUND)) { + return false; + } + + if (Objects.equals(copied.getBackground(), textData.getBackground())) { + MessageHelper.warning(player, "This hologram already has this background color"); + return false; + } + + textData.setBackground(copied.getBackground()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed background color"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BillboardCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BillboardCMD.java new file mode 100644 index 00000000..b76762e5 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BillboardCMD.java @@ -0,0 +1,73 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.base.Enums; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class BillboardCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.billboard"))) { + MessageHelper.error(player, "You don't have the required permission to change the billboard of a hologram"); + return false; + } + + final var billboard = Enums.getIfPresent(Display.Billboard.class, args[3].toUpperCase(Locale.ROOT)).orNull(); + + if (billboard == null) { + MessageHelper.error(player, "Could not parse billboard"); + return false; + } + + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if (billboard == displayData.getBillboard()) { + MessageHelper.warning(player, "This billboard is already set"); + return false; + } + + final var copied = displayData.copy(displayData.getName()); + copied.setBillboard(billboard); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.BILLBOARD)) { + return false; + } + + if (copied.getBillboard() == displayData.getBillboard()) { + MessageHelper.warning(player, "This billboard is already set"); + return false; + } + + displayData.setBillboard(copied.getBillboard()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed the billboard to " + StringUtils.capitalize(billboard.name().toLowerCase(Locale.ROOT))); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BlockCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BlockCMD.java new file mode 100644 index 00000000..a26269a3 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BlockCMD.java @@ -0,0 +1,68 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.BlockHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BlockCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.block"))) { + MessageHelper.error(player, "You don't have the required permission to change the block of this hologram"); + return false; + } + + if (!(hologram.getData() instanceof BlockHologramData blockData)) { + MessageHelper.error(player, "This command can only be used on item holograms"); + return false; + } + Material block = Material.getMaterial(args[3]); + if (block == null) { + MessageHelper.error(player, "Could not find block type"); + return false; + } + + if (block == blockData.getBlock()) { + MessageHelper.warning(player, "This block is already set"); + return false; + } + + final var copied = blockData.copy(blockData.getName()); + copied.setBlock(block); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.BILLBOARD)) { + return false; + } + + if (copied.getBlock() == blockData.getBlock()) { + MessageHelper.warning(player, "This block is already set"); + return false; + } + + blockData.setBlock(block); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Set block to '" + block.name() + "'"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BrightnessCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BrightnessCMD.java new file mode 100644 index 00000000..688054b0 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/BrightnessCMD.java @@ -0,0 +1,71 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.NumberHelper; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BrightnessCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if(args.length < 5) { + MessageHelper.error(player, "You must provide a brightness type and value."); + return false; + } + + final var brightnessType = args[3]; + + if(!brightnessType.equalsIgnoreCase("block") && !brightnessType.equalsIgnoreCase("sky")) { + MessageHelper.error(player, "Invalid brightness type, valid options are BLOCK or SKY"); + return false; + } + + final var parsedNumber = NumberHelper.parseInt(args[4]); + + if(parsedNumber.isEmpty()) { + MessageHelper.error(player, "Invalid brightness value."); + return false; + } + + final var brightnessValue = parsedNumber.get(); + + if(brightnessValue < 0 || brightnessValue > 15) { + MessageHelper.error(player, "Invalid brightness value, must be between 0 and 15"); + return false; + } + + final var currentBrightness = displayData.getBrightness(); + final var blockBrightness = brightnessType.equalsIgnoreCase("block") ? brightnessValue : + currentBrightness == null ? 0 : currentBrightness.getBlockLight(); + final var skyBrightness = brightnessType.equalsIgnoreCase("sky") ? brightnessValue : + currentBrightness == null ? 0 : currentBrightness.getSkyLight(); + + displayData.setBrightness(new Display.Brightness(blockBrightness, skyBrightness)); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed " + brightnessType.toLowerCase() + " brightness to " + brightnessValue); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CenterCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CenterCMD.java new file mode 100644 index 00000000..83f97cf2 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CenterCMD.java @@ -0,0 +1,52 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class CenterCMD implements Subcommand { + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.center"))) { + MessageHelper.error(player, "You don't have the required permission to center a hologram"); + return false; + } + + Location location = hologram.getData().getLocation(); + + location.set( + Math.floor(location.x()) + 0.5, + location.y(), + Math.floor(location.z()) + 0.5 + ); + + hologram.getData().setLocation(location); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Centered the hologram to %s/%s/%s %s\u00B0 %s\u00B0".formatted( + Constants.COORDINATES_DECIMAL_FORMAT.format(location.x()), + Constants.COORDINATES_DECIMAL_FORMAT.format(location.y()), + Constants.COORDINATES_DECIMAL_FORMAT.format(location.z()), + Constants.COORDINATES_DECIMAL_FORMAT.format((location.getYaw() + 180f) % 360f), + Constants.COORDINATES_DECIMAL_FORMAT.format((location.getPitch()) % 360f) + )); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CopyCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CopyCMD.java new file mode 100644 index 00000000..e1e7117d --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CopyCMD.java @@ -0,0 +1,83 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.events.HologramCreateEvent; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class CopyCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + if (!(sender.hasPermission("fancyholograms.hologram.copy"))) { + MessageHelper.error(sender, "You don't have the required permission to clone a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + + if (args.length < 3) { + MessageHelper.error(sender, "Wrong usage: /hologram help"); + return false; + } + + String name = args[2]; + + if (FancyHolograms.get().getHologramsManager().getHologram(name).isPresent()) { + MessageHelper.error(sender, "There already exists a hologram with this name"); + return false; + } + + if (name.contains(".")) { + MessageHelper.error(sender, "The name of the hologram cannot contain a dot"); + return false; + } + + final var data = hologram.getData().copy(name); + Location originalLocation = data.getLocation(); + Location location = player.getLocation(); + location.setPitch(originalLocation.getPitch()); + location.setYaw(originalLocation.getYaw()); + data.setLocation(location); + + final var copy = FancyHolograms.get().getHologramsManager().create(data); + + if (!new HologramCreateEvent(copy, player).callEvent()) { + MessageHelper.error(sender, "Creating the copied hologram was cancelled"); + return false; + } + + copy.createHologram(); + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + copy.updateShownStateFor(onlinePlayer); + } + + FancyHolograms.get().getHologramsManager().addHologram(copy); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(sender, "Copied the hologram"); + return true; + + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CreateCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CreateCMD.java new file mode 100644 index 00000000..d85ccf65 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/CreateCMD.java @@ -0,0 +1,90 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.hologram.HologramType; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramCreateEvent; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class CreateCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.create"))) { + MessageHelper.error(sender, "You don't have the required permission to create a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + if (args.length < 3) { + MessageHelper.error(player, "Wrong usage: /hologram help"); + return false; + } + + HologramType type = HologramType.getByName(args[1]); + if (type == null) { + MessageHelper.error(player, "Could not find type: " + args[1]); + return false; + } + + String name = args[2]; + + if (FancyHolograms.get().getHologramsManager().getHologram(name).isPresent()) { + MessageHelper.error(player, "There already exists a hologram with this name"); + return false; + } + + if (name.contains(".")) { + MessageHelper.error(player, "The name of the hologram cannot contain a dot"); + return false; + } + + DisplayHologramData displayData = null; + switch (type) { + case TEXT -> displayData = new TextHologramData(name, player.getLocation()); + case ITEM -> { + displayData = new ItemHologramData(name, player.getLocation()); + displayData.setBillboard(Display.Billboard.FIXED); + } + case BLOCK -> { + displayData = new BlockHologramData(name, player.getLocation()); + displayData.setBillboard(Display.Billboard.FIXED); + } + } + + final var holo = FancyHolograms.get().getHologramsManager().create(displayData); + if (!new HologramCreateEvent(holo, player).callEvent()) { + MessageHelper.error(player, "Creating the hologram was cancelled"); + return false; + } + + holo.createHologram(); + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + holo.updateShownStateFor(onlinePlayer); + } + + FancyHolograms.get().getHologramsManager().addHologram(holo); + + MessageHelper.success(player, "Created the hologram"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InfoCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InfoCMD.java new file mode 100644 index 00000000..d147cce8 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InfoCMD.java @@ -0,0 +1,82 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.List; + +public class InfoCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.info"))) { + MessageHelper.error(player, "You don't have the required permission to view the info of a hologram"); + return false; + } + + HologramData data = hologram.getData(); + + MessageHelper.info(player, "Information about the " + hologram.getData().getName() + " hologram:"); + MessageHelper.info(player, "Name: " + hologram.getData().getName()); + MessageHelper.info(player, "Type: " + hologram.getData().getType().name()); + MessageHelper.info(player, "Location: " + data.getLocation().getWorld().getName() + " " + data.getLocation().getX() + " / " + data.getLocation().getY() + " / " + data.getLocation().getZ()); + MessageHelper.info(player, "Visibility distance: " + data.getVisibilityDistance() + " blocks"); + + if (data instanceof DisplayHologramData displayData) { + Vector3f scale = displayData.getScale(); + if (scale.x() == scale.y() && scale.y() == scale.z()) { + MessageHelper.info(player, "Scale: x" + displayData.getScale().x()); + } else { + MessageHelper.info(player, "Scale: " + displayData.getScale().x() + ", " + displayData.getScale().y() + ", " + displayData.getScale().z()); + } + + MessageHelper.info(player, "Billboard: " + displayData.getBillboard().name()); + MessageHelper.info(player, "Shadow radius: " + displayData.getShadowRadius()); + MessageHelper.info(player, "Shadow strength: " + displayData.getShadowStrength()); + } + + if (data.getLinkedNpcName() != null) { + MessageHelper.info(player, "Linked npc: " + data.getLinkedNpcName()); + } + + if (data instanceof TextHologramData textData) { + MessageHelper.info(player, "Text: "); + for (String line : textData.getText()) { + MessageHelper.info(player, " " + line); + } + + if (textData.getBackground() != null) { + MessageHelper.info(player, "Background: " + '#' + Integer.toHexString(textData.getBackground().asARGB())); + } else { + MessageHelper.info(player, "Background: default"); + } + + MessageHelper.info(player, "Text alignment: " + textData.getTextAlignment().name()); + MessageHelper.info(player, "See through: " + (textData.isSeeThrough() ? "enabled" : "disabled")); + MessageHelper.info(player, "Text shadow: " + (textData.hasTextShadow() ? "enabled" : "disabled")); + if (textData.getTextUpdateInterval() == -1) { + MessageHelper.info(player, "Update text interval: not updating"); + } else { + MessageHelper.info(player, "Update text interval: " + textData.getTextUpdateInterval() + " ticks"); + } + } else if (data instanceof BlockHologramData blockData) { + MessageHelper.info(player, "Block: " + blockData.getBlock().name()); + } else if (data instanceof ItemHologramData itemData) { + MessageHelper.info(player, "Item: " + itemData.getItemStack().getType().name()); + } + + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertAfterCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertAfterCMD.java new file mode 100644 index 00000000..7b468434 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertAfterCMD.java @@ -0,0 +1,80 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class InsertAfterCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.insert_after"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + var index = Ints.tryParse(args[3]); + if (index == null) { + MessageHelper.error(player, "Could not parse line number"); + return false; + } + + if (index < 0) { + MessageHelper.error(player, "Invalid line index"); + return false; + } + + String text = ""; + for (int i = 4; i < args.length; i++) { + text += args[i] + " "; + } + + if (text.isEmpty()) { + MessageHelper.error(player, "You need to provide a text to insert"); + return true; + } + + text = text.substring(0, text.length() - 1); + + final var lines = new ArrayList<>(textData.getText()); + lines.add(Math.min(index, lines.size()), text); + + final var copied = textData.copy(textData.getName()); + copied.setText(lines); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TEXT)) { + return false; + } + + textData.setText(copied.getText()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Inserted line"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertBeforeCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertBeforeCMD.java new file mode 100644 index 00000000..2affa56d --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/InsertBeforeCMD.java @@ -0,0 +1,82 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class InsertBeforeCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.insert_before"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + var index = Ints.tryParse(args[3]); + if (index == null) { + MessageHelper.error(player, "Could not parse line number"); + return false; + } + + index--; + + if (index < 0) { + MessageHelper.error(player, "Invalid line index"); + return false; + } + + String text = ""; + for (int i = 4; i < args.length; i++) { + text += args[i] + " "; + } + + if (text.isEmpty()) { + MessageHelper.error(player, "You need to provide a text to insert"); + return true; + } + + text = text.substring(0, text.length() - 1); + + final var lines = new ArrayList<>(textData.getText()); + lines.add(Math.min(index, lines.size()), text); + + final var copied = textData.copy(textData.getName()); + copied.setText(lines); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TEXT)) { + return false; + } + + textData.setText(copied.getText()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Inserted line"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ItemCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ItemCMD.java new file mode 100644 index 00000000..3a8323b1 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ItemCMD.java @@ -0,0 +1,76 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.ItemHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ItemCMD implements Subcommand { + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.edit.item"))) { + MessageHelper.error(sender, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + if (!(hologram.getData() instanceof ItemHologramData itemData)) { + MessageHelper.error(player, "This command can only be used on item holograms"); + return false; + } + + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() == Material.AIR || item.getAmount() < 1) { + MessageHelper.error(player, "You need to hold an item in your hand"); + return false; + } + + + if (item == itemData.getItemStack()) { + MessageHelper.warning(player, "This item is already set"); + return false; + } + + final var copied = itemData.copy(itemData.getName()); + copied.setItemStack(item); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.BILLBOARD)) { + return false; + } + + if (copied.getItemStack() == itemData.getItemStack()) { + MessageHelper.warning(player, "This item is already set"); + return false; + } + + itemData.setItemStack(item); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Set the item to '" + item.getType().name() + "'"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/LinkWithNpcCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/LinkWithNpcCMD.java new file mode 100644 index 00000000..284fe0d4 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/LinkWithNpcCMD.java @@ -0,0 +1,60 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.PluginUtils; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancynpcs.api.FancyNpcsPlugin; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class LinkWithNpcCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.link"))) { + MessageHelper.error(player, "You don't have the required permission to link a hologram"); + return false; + } + + if (!PluginUtils.isFancyNpcsEnabled()) { + MessageHelper.warning(player, "You need to install the FancyNpcs plugin for this functionality to work"); + MessageHelper.warning(player, "Download link: click here."); + return false; + } + + String name = args[3]; + + if (hologram.getData().getLinkedNpcName() != null) { + MessageHelper.error(player, "This hologram is already linked with an NPC"); + return false; + } + + final var npc = FancyNpcsPlugin.get().getNpcManager().getNpc(name); + if (npc == null) { + MessageHelper.error(player, "Could not find NPC with that name"); + return false; + } + + hologram.getData().setLinkedNpcName(npc.getData().getName()); + + FancyHolograms.get().getHologramsManager().syncHologramWithNpc(hologram); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Linked hologram with NPC"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ListCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ListCMD.java new file mode 100644 index 00000000..bf296ded --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ListCMD.java @@ -0,0 +1,78 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ListCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.list"))) { + MessageHelper.error(player, "You don't have the required permission to list the holograms"); + return false; + } + + final var holograms = FancyHolograms.get().getHologramsManager().getPersistentHolograms(); + + if (holograms.isEmpty()) { + MessageHelper.warning(player, "There are no holograms. Use '/hologram create' to create one"); + } else { + int page; + if (args.length < 2) { + page = 1; + } else { + final var index = Ints.tryParse(args[1]); + if (index == null) { + MessageHelper.error(player, "Could not parse page number"); + return false; + } + page = index; + } + + var pages = holograms.size() / 10 + 1; + if (page > pages) { + MessageHelper.error(player, "Page %s does not exist".formatted(page)); + return true; + } + MessageHelper.info(player, "List of holograms:"); + MessageHelper.info(player, "Page %s/%s".formatted(page, pages)); + holograms.stream() + .skip((page - 1) * 10) + .limit(10) + .forEach(holo -> { + final var location = holo.getData().getLocation(); + if (location == null || location.getWorld() == null) { + return; + } + + MessageHelper.info(player, + "Click to teleport'> - %s (%s/%s/%s in %s)" + .formatted("/hologram teleport " + holo.getData().getName(), + holo.getData().getName(), + Constants.DECIMAL_FORMAT.format(location.x()), + Constants.DECIMAL_FORMAT.format(location.y()), + Constants.DECIMAL_FORMAT.format(location.z()), + location.getWorld().getName() + )); + }); + + } + + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveHereCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveHereCMD.java new file mode 100644 index 00000000..dd603cc7 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveHereCMD.java @@ -0,0 +1,94 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Doubles; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancylib.MessageHelper; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; + +public class MoveHereCMD implements Subcommand { + + public static boolean setLocation(Player player, Hologram hologram, Location location, boolean applyRotation) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.move_here"))) { + MessageHelper.error(player, "You don't have the required permission to move a hologram"); + return false; + } + + final var copied = hologram.getData().copy(hologram.getName()); + final Location newLocation = (applyRotation) + ? location + : new Location(location.getWorld(), location.x(), location.y(), location.z(), copied.getLocation().getYaw(), copied.getLocation().getPitch()); + copied.setLocation(newLocation); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.POSITION)) { + return false; + } + + hologram.getData().setLocation(copied.getLocation()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Moved the hologram to %s/%s/%s %s\u00B0 %s\u00B0".formatted( + Constants.COORDINATES_DECIMAL_FORMAT.format(newLocation.x()), + Constants.COORDINATES_DECIMAL_FORMAT.format(newLocation.y()), + Constants.COORDINATES_DECIMAL_FORMAT.format(newLocation.z()), + Constants.COORDINATES_DECIMAL_FORMAT.format((newLocation.getYaw() + 180f) % 360f), + Constants.COORDINATES_DECIMAL_FORMAT.format((newLocation.getPitch()) % 360f) + )); + + return true; + } + + public static @Nullable Double calculateCoordinate(@NotNull final String text, @Nullable final Location originLocation, @NotNull final Location callerLocation, @NotNull final Function extractor) { + final var number = Doubles.tryParse(StringUtils.stripStart(text, "~")); + final var target = text.startsWith("~~") ? callerLocation : text.startsWith("~") ? originLocation : null; + + if (number == null) { + return target == null ? null : extractor.apply(target).doubleValue(); + } + + if (target == null) { + return number; + } + + return number + extractor.apply(target).doubleValue(); + } + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + if (hologram.getData().getLinkedNpcName() != null) { + MessageHelper.error(player, "This hologram is linked with an NPC"); + MessageHelper.error(player, "To unlink: /hologram edit " + hologram.getData().getName() + " unlinkWithNpc"); + return false; + } + + final var location = player.getLocation(); + + return setLocation(player, hologram, location, false); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveToCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveToCMD.java new file mode 100644 index 00000000..f921c580 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/MoveToCMD.java @@ -0,0 +1,73 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class MoveToCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.edit.move_to"))) { + MessageHelper.error(sender, "You don't have the required permission to move a hologram"); + return false; + } + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + if (args.length < 3) { + MessageHelper.error(player, "Wrong usage: /hologram help"); + return false; + } + + final var x = MoveHereCMD.calculateCoordinate(args[3], hologram.getData().getLocation(), player.getLocation(), Location::x); + final var y = MoveHereCMD.calculateCoordinate(args[4], hologram.getData().getLocation(), player.getLocation(), Location::y); + final var z = MoveHereCMD.calculateCoordinate(args[5], hologram.getData().getLocation(), player.getLocation(), Location::z); + + if (x == null || y == null || z == null) { + MessageHelper.error(player, "Could not parse position"); + return false; + } + + final var location = new Location(player.getWorld(), x, y, z, hologram.getData().getLocation().getYaw(), hologram.getData().getLocation().getPitch()); + + if (args.length > 6) { + final var yaw = MoveHereCMD.calculateCoordinate(args[6], hologram.getData().getLocation(), player.getLocation(), loc -> loc.getYaw() + 180f); + + if (yaw == null) { + MessageHelper.error(player, "Could not parse yaw"); + return false; + } + + location.setYaw(yaw.floatValue() - 180f); + } + + if (args.length > 7) { + final var pitch = MoveHereCMD.calculateCoordinate(args[7], hologram.getData().getLocation(), player.getLocation(), Location::getPitch); + + if (pitch == null) { + MessageHelper.error(player, "Could not parse pitch"); + return false; + } + + location.setPitch(pitch.floatValue()); + } + + return MoveHereCMD.setLocation(player, hologram, location, true); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/NearbyCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/NearbyCMD.java new file mode 100644 index 00000000..bd1868c3 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/NearbyCMD.java @@ -0,0 +1,94 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.Constants; +import de.oliver.fancyholograms.util.NumberHelper; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class NearbyCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.nearby"))) { + MessageHelper.error(player, "You don't have the required permission to see nearby holograms"); + return false; + } + + if (!(player instanceof Player)) { + MessageHelper.error(player, "This is a player only command."); + return false; + } + + + if (args.length < 2) { + MessageHelper.error(player, Constants.INVALID_NEARBY_RANGE); + return false; + } + + Optional range = NumberHelper.parseInt(args[1]); + + if (range.isEmpty()) { + MessageHelper.error(player, Constants.INVALID_NEARBY_RANGE); + return false; + } + + Location playerLocation = ((Player) player).getLocation().clone(); + + List> nearby = FancyHolograms.get() + .getHologramsManager() + .getPersistentHolograms() + .stream() + .filter((holo) -> holo.getData().getLocation().getWorld() == playerLocation.getWorld()) + .map((holo) -> Map.entry(holo, holo.getData().getLocation().distance(playerLocation))) + .filter((entry) -> entry.getValue() <= range.get()) + .sorted(Comparator.comparingInt(a -> a.getValue().intValue())) + .toList(); + + if (nearby.isEmpty()) { + MessageHelper.error(player, "There are no nearby holograms in a radius of %s blocks.".formatted(range.get())); + return true; + } + + MessageHelper.info(player, "Holograms nearby (%s radius)".formatted(range.get())); + nearby.forEach((entry) -> { + Hologram holo = entry.getKey(); + double distance = entry.getValue(); + + final var location = holo.getData().getLocation(); + if (location == null || location.getWorld() == null) { + return; + } + + MessageHelper.info(player, + "Click to teleport'> - %s (%s/%s/%s in %s, %s blocks away)" + .formatted( + "/hologram teleport " + holo.getData().getName(), + holo.getData().getName(), + Constants.DECIMAL_FORMAT.format(location.x()), + Constants.DECIMAL_FORMAT.format(location.y()), + Constants.DECIMAL_FORMAT.format(location.z()), + location.getWorld().getName(), + Constants.DECIMAL_FORMAT.format(distance) + )); + }); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveCMD.java new file mode 100644 index 00000000..d978ad66 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveCMD.java @@ -0,0 +1,42 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.events.HologramDeleteEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class RemoveCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.remove"))) { + MessageHelper.error(player, "You don't have the required permission to remove a hologram"); + return false; + } + + if (!new HologramDeleteEvent(hologram, player).callEvent()) { + MessageHelper.error(player, "Removing the hologram was cancelled"); + return false; + } + + FancyHologramsPlugin.get().getHologramThread().submit(() -> { + FancyHolograms.get().getHologramsManager().removeHologram(hologram); + MessageHelper.success(player, "Removed the hologram"); + }); + + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveLineCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveLineCMD.java new file mode 100644 index 00000000..26b93612 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RemoveLineCMD.java @@ -0,0 +1,42 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class RemoveLineCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.line.remove"))) { + MessageHelper.error(player, "You don't have the required permission to remove a line from a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var index = Ints.tryParse(args[3]); + if (index == null) { + MessageHelper.error(player, "Could not parse line number"); + return false; + } + + return SetLineCMD.setLine(player, hologram, index - 1, null); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotateCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotateCMD.java new file mode 100644 index 00000000..ea2be61c --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotateCMD.java @@ -0,0 +1,40 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class RotateCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.edit.rotate"))) { + MessageHelper.error(sender, "You don't have the required permission to rotate a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + final var yaw = MoveHereCMD.calculateCoordinate(args[3], hologram.getData().getLocation(), player.getLocation(), loc -> loc.getYaw() + 180f); + Location location = hologram.getData().getLocation().clone(); + location.setYaw(yaw.floatValue() - 180f); + + return MoveHereCMD.setLocation(player, hologram, location, true); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotatePitchCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotatePitchCMD.java new file mode 100644 index 00000000..8757be35 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/RotatePitchCMD.java @@ -0,0 +1,40 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class RotatePitchCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.edit.rotate_pitch"))) { + MessageHelper.error(sender, "You don't have the required permission to rotate a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + final var pitch = MoveHereCMD.calculateCoordinate(args[3], hologram.getData().getLocation(), player.getLocation(), loc -> loc.getPitch() - 180f); + Location location = hologram.getData().getLocation().clone(); + location.setPitch(pitch.floatValue()); + + return MoveHereCMD.setLocation(player, hologram, location, true); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ScaleCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ScaleCMD.java new file mode 100644 index 00000000..38612f93 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ScaleCMD.java @@ -0,0 +1,80 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Floats; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.List; + +public class ScaleCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.scale"))) { + MessageHelper.error(player, "You don't have the required permission to change the scale of a hologram"); + return false; + } + + final var scaleX = Floats.tryParse(args[3]); + final var scaleY = args.length >= 6 ? Floats.tryParse(args[4]) : scaleX; + final var scaleZ = args.length >= 6 ? Floats.tryParse(args[5]) : scaleX; + + if (scaleX == null || scaleY == null || scaleZ == null) { + MessageHelper.error(player, "Could not parse scale"); + return false; + } + + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if (Float.compare(scaleX, displayData.getScale().x()) == 0 && + Float.compare(scaleY, displayData.getScale().y()) == 0 && + Float.compare(scaleZ, displayData.getScale().z()) == 0) { + MessageHelper.warning(player, "This hologram is already at this scale"); + return false; + } + + final var copied = displayData.copy(displayData.getName()); + copied.setScale(new Vector3f(scaleX, scaleY, scaleZ)); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.SCALE)) { + return false; + } + + if (Float.compare(copied.getScale().x(), displayData.getScale().x()) == 0 && + Float.compare(copied.getScale().y(), displayData.getScale().y()) == 0 && + Float.compare(copied.getScale().z(), displayData.getScale().z()) == 0) { + MessageHelper.warning(player, "This hologram is already at this scale"); + return false; + } + + displayData.setScale(new Vector3f( + copied.getScale().x(), + copied.getScale().y(), + copied.getScale().z())); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed scale to " + scaleX + ", " + scaleY + ", " + scaleZ); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SeeThroughCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SeeThroughCMD.java new file mode 100644 index 00000000..a4468440 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SeeThroughCMD.java @@ -0,0 +1,74 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class SeeThroughCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.see_trough"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var enabled = switch (args[3].toLowerCase(Locale.ROOT)) { + case "true" -> true; + case "false" -> false; + default -> null; + }; + + if (enabled == null) { + MessageHelper.error(player, "Could not parse see through flag"); + return false; + } + + if (enabled == textData.isSeeThrough()) { + MessageHelper.warning(player, "This hologram already has see through " + (enabled ? "enabled" : "disabled")); + return false; + } + + final var copied = textData.copy(textData.getName()); + copied.setSeeThrough(enabled); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.SEE_THROUGH)) { + return false; + } + + if (enabled == textData.isSeeThrough()) { + MessageHelper.warning(player, "This hologram already has see through " + (enabled ? "enabled" : "disabled")); + return false; + } + + textData.setSeeThrough(copied.isSeeThrough()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed see through"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SetLineCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SetLineCMD.java new file mode 100644 index 00000000..a240cc3e --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/SetLineCMD.java @@ -0,0 +1,87 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class SetLineCMD implements Subcommand { + + public static boolean setLine(CommandSender player, Hologram hologram, int index, String text) { + + if (!(player.hasPermission("fancyholograms.hologram.line.set"))) { + MessageHelper.error(player, "You don't have the required permission to set a line to this hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var lines = new ArrayList<>(textData.getText()); + + if (index >= lines.size()) { + lines.add(text == null ? " " : text); + } else if (text == null) { + lines.remove(index); + } else { + lines.set(index, text); + } + + final var copied = textData.copy(textData.getName()); + copied.setText(lines); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TEXT)) { + return false; + } + + textData.setText(copied.getText()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed text for line " + (Math.min(index, lines.size() - 1) + 1)); + return true; + } + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + var index = Ints.tryParse(args[3]); + if (index == null) { + MessageHelper.error(player, "Could not parse line number"); + return false; + } + + if (index < 0) { + MessageHelper.error(player, "Invalid line index"); + return false; + } + + index--; + + String text = ""; + for (int i = 4; i < args.length; i++) { + text += args[i] + " "; + } + text = text.substring(0, text.length() - 1); + + return setLine(player, hologram, index, text); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowRadiusCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowRadiusCMD.java new file mode 100644 index 00000000..28105469 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowRadiusCMD.java @@ -0,0 +1,70 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Floats; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ShadowRadiusCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.shadow_radius"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + final var radius = Floats.tryParse(args[3]); + + if (radius == null) { + MessageHelper.error(player, "Could not parse shadow radius"); + return false; + } + + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if (Float.compare(radius, displayData.getShadowRadius()) == 0) { + MessageHelper.warning(player, "This hologram already has this shadow radius"); + return false; + } + + final var copied = displayData.copy(displayData.getName()); + copied.setShadowRadius(radius); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.SHADOW_RADIUS)) { + return false; + } + + if (Float.compare(copied.getShadowRadius(), displayData.getShadowRadius()) == 0) { + MessageHelper.warning(player, "This hologram already has this shadow radius"); + return false; + } + + displayData.setShadowRadius(copied.getShadowRadius()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed shadow radius"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowStrengthCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowStrengthCMD.java new file mode 100644 index 00000000..40daac04 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/ShadowStrengthCMD.java @@ -0,0 +1,70 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Floats; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ShadowStrengthCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.shadow_strength"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + final var strength = Floats.tryParse(args[3]); + + if (strength == null) { + MessageHelper.error(player, "Could not parse shadow strength"); + return false; + } + + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if (Float.compare(strength, displayData.getShadowStrength()) == 0) { + MessageHelper.warning(player, "This hologram already has this shadow strength"); + return false; + } + + final var copied = displayData.copy(displayData.getName()); + copied.setShadowStrength(strength); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.SHADOW_STRENGTH)) { + return false; + } + + if (Float.compare(copied.getShadowStrength(), displayData.getShadowStrength()) == 0) { + MessageHelper.warning(player, "This hologram already has this shadow strength"); + return false; + } + + displayData.setShadowStrength(copied.getShadowStrength()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed shadow strength"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TeleportCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TeleportCMD.java new file mode 100644 index 00000000..9335a716 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TeleportCMD.java @@ -0,0 +1,47 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class TeleportCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender sender, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(sender.hasPermission("fancyholograms.hologram.teleport"))) { + MessageHelper.error(sender, "You don't have the required permission to teleport you to a hologram"); + return false; + } + + if (!(sender instanceof Player player)) { + MessageHelper.error(sender, "You must be a sender to use this command"); + return false; + } + + final var location = hologram.getData().getLocation(); + + if (location == null || location.getWorld() == null) { + MessageHelper.error(player, "Could not teleport to the hologram"); + return false; + } + + player.teleportAsync(location).thenAccept(success -> { + if (success) MessageHelper.success(player, "Teleported you to the hologram"); + else MessageHelper.error(player, "Could not teleport to the hologram"); + }); + + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextAlignmentCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextAlignmentCMD.java new file mode 100644 index 00000000..a4e3e259 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextAlignmentCMD.java @@ -0,0 +1,72 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.base.Enums; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.TextDisplay; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class TextAlignmentCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.text_alignment"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var alignment = Enums.getIfPresent(TextDisplay.TextAlignment.class, args[3].toUpperCase(Locale.ROOT)).orNull(); + + if (alignment == null) { + MessageHelper.error(player, "Could not parse text alignment"); + return false; + } + + if (textData.getTextAlignment() == alignment) { + MessageHelper.warning(player, "This hologram already has this text alignment"); + return false; + } + + final var copied = textData.copy(textData.getName()); + copied.setTextAlignment(alignment); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TEXT_ALIGNMENT)) { + return false; + } + + if (textData.getTextAlignment() == alignment) { + MessageHelper.warning(player, "This hologram already has this text alignment"); + return false; + } + + textData.setTextAlignment(((TextHologramData) copied).getTextAlignment()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed text alignment"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextShadowCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextShadowCMD.java new file mode 100644 index 00000000..2564497d --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TextShadowCMD.java @@ -0,0 +1,74 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class TextShadowCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.text_shadow"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var enabled = switch (args[3].toLowerCase(Locale.ROOT)) { + case "true" -> true; + case "false" -> false; + default -> null; + }; + + if (enabled == null) { + MessageHelper.error(player, "Could not parse text shadow flag"); + return false; + } + + if (enabled == textData.hasTextShadow()) { + MessageHelper.warning(player, "This hologram already has text shadow " + (enabled ? "enabled" : "disabled")); + return false; + } + + final var copied = textData.copy(textData.getName()); + copied.setTextShadow(enabled); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TEXT_SHADOW)) { + return false; + } + + if (enabled == textData.hasTextShadow()) { + MessageHelper.warning(player, "This hologram already has text shadow " + (enabled ? "enabled" : "disabled")); + return false; + } + + textData.setTextShadow(copied.hasTextShadow()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed text shadow"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TranslateCommand.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TranslateCommand.java new file mode 100644 index 00000000..0e405489 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/TranslateCommand.java @@ -0,0 +1,80 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Floats; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.List; + +public class TranslateCommand implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.translate"))) { + MessageHelper.error(player, "You don't have the required permission to change the translation of a hologram"); + return false; + } + + final var translateX = Floats.tryParse(args[3]); + final var translateY = args.length >= 6 ? Floats.tryParse(args[4]) : translateX; + final var translateZ = args.length >= 6 ? Floats.tryParse(args[5]) : translateX; + + if (translateX == null || translateY == null || translateZ == null) { + MessageHelper.error(player, "Could not parse translation"); + return false; + } + + if (!(hologram.getData() instanceof DisplayHologramData displayData)) { + MessageHelper.error(player, "This command can only be used on display holograms"); + return false; + } + + if (Float.compare(translateX, displayData.getTranslation().x()) == 0 && + Float.compare(translateY, displayData.getTranslation().y()) == 0 && + Float.compare(translateZ, displayData.getTranslation().z()) == 0) { + MessageHelper.warning(player, "This hologram is already at this translation"); + return false; + } + + final var copied = displayData.copy(displayData.getName()); + copied.setTranslation(new Vector3f(translateX, translateY, translateZ)); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.TRANSLATION)) { + return false; + } + + if (Float.compare(copied.getTranslation().x(), displayData.getTranslation().x()) == 0 && + Float.compare(copied.getTranslation().y(), displayData.getTranslation().y()) == 0 && + Float.compare(copied.getTranslation().z(), displayData.getTranslation().z()) == 0) { + MessageHelper.warning(player, "This hologram is already at this translation"); + return false; + } + + displayData.setTranslation(new Vector3f( + copied.getTranslation().x(), + copied.getTranslation().y(), + copied.getTranslation().z())); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed translation to " + translateX + ", " + translateY + ", " + translateZ); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UnlinkWithNpcCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UnlinkWithNpcCMD.java new file mode 100644 index 00000000..09d9a3ef --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UnlinkWithNpcCMD.java @@ -0,0 +1,57 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancyholograms.util.PluginUtils; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancynpcs.api.FancyNpcsPlugin; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class UnlinkWithNpcCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.unlink"))) { + MessageHelper.error(player, "You don't have the required permission to unlink a hologram"); + return false; + } + + if (!PluginUtils.isFancyNpcsEnabled()) { + MessageHelper.warning(player, "You need to install the FancyNpcs plugin for this functionality to work"); + MessageHelper.warning(player, "Download link: click here."); + return false; + } + + if (hologram.getData().getLinkedNpcName() == null) { + MessageHelper.error(player, "This hologram is not linked with an NPC"); + return false; + } + + final var npc = FancyNpcsPlugin.get().getNpcManager().getNpc(hologram.getData().getLinkedNpcName()); + + hologram.getData().setLinkedNpcName(null); + + if (npc != null) { + npc.getData().setDisplayName(npc.getData().getName()); + npc.updateForAll(); + } + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Unlinked hologram with NPC"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UpdateTextIntervalCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UpdateTextIntervalCMD.java new file mode 100644 index 00000000..712b1a9f --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/UpdateTextIntervalCMD.java @@ -0,0 +1,97 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; + +public class UpdateTextIntervalCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.text_interval"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + if (!(hologram.getData() instanceof TextHologramData textData)) { + MessageHelper.error(player, "This command can only be used on text holograms"); + return false; + } + + final var text = args[3].toLowerCase(Locale.ROOT); + + Integer interval; + + if (text.equals("never") || text.equals("off") || text.equals("none")) { + interval = -1; + } else { + + var multiplier = 1; + + if (!text.isEmpty()) { + switch (text.charAt(text.length() - 1)) { + case 's' -> multiplier = 1000; + case 'm' -> multiplier = 1000 * 60; + } + } + + final var time = Ints.tryParse(multiplier == 1 ? text : text.substring(0, text.length() - 1)); + + if (time == null) { + interval = null; + } else { + interval = time * multiplier; + } + } + + if (interval == null) { + MessageHelper.error(player, "Could not parse text update interval"); + return false; + } + + if (interval == textData.getTextUpdateInterval()) { + MessageHelper.warning(player, "This hologram already has this text update interval"); + return false; + } + + interval = Math.max(-1, interval); + + final var copied = textData.copy(textData.getName()); + copied.setTextUpdateInterval(interval); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.UPDATE_TEXT_INTERVAL)) { + return false; + } + + if (copied.getTextUpdateInterval() == textData.getTextUpdateInterval()) { + MessageHelper.warning(player, "This hologram already has this text update interval"); + return false; + } + + textData.setTextUpdateInterval(copied.getTextUpdateInterval()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed the text update interval"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java new file mode 100644 index 00000000..2152c213 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java @@ -0,0 +1,56 @@ +package de.oliver.fancyholograms.commands.hologram; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.data.property.Visibility; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class VisibilityCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return Arrays.stream( + Visibility.values() + ).map(Objects::toString).toList(); + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.visibility"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + final var optionalVisibility = Visibility.byString(args[3]); + if (hologram == null || optionalVisibility.isEmpty()) { + return false; + } + final var visibility = optionalVisibility.get(); + + final var copied = hologram.getData().copy(hologram.getName()); + copied.setVisibility(visibility); + + if (hologram.getData().getVisibility() == copied.getVisibility()) { + MessageHelper.warning(player, "This hologram already has visibility set to " + visibility); + return false; + } + + hologram.getData().setVisibility(copied.getVisibility()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed visibility to " + visibility); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityDistanceCMD.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityDistanceCMD.java new file mode 100644 index 00000000..97c092b1 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityDistanceCMD.java @@ -0,0 +1,68 @@ +package de.oliver.fancyholograms.commands.hologram; + +import com.google.common.primitives.Ints; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.events.HologramUpdateEvent; +import de.oliver.fancyholograms.commands.HologramCMD; +import de.oliver.fancyholograms.commands.Subcommand; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class VisibilityDistanceCMD implements Subcommand { + + @Override + public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + return null; + } + + @Override + public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { + + if (!(player.hasPermission("fancyholograms.hologram.edit.visibility_distance"))) { + MessageHelper.error(player, "You don't have the required permission to edit a hologram"); + return false; + } + + var visibilityDistance = Ints.tryParse(args[3]); + + if (visibilityDistance == null) { + MessageHelper.error(player, "Could not parse visibility distance"); + return false; + } + + if (visibilityDistance <= 0) { + visibilityDistance = -1; + } + + if (Ints.compare(visibilityDistance, hologram.getData().getVisibilityDistance()) == 0) { + MessageHelper.warning(player, "This hologram already has this visibility distance"); + return false; + } + + final var copied = hologram.getData().copy(hologram.getName()); + copied.setVisibilityDistance(visibilityDistance); + + if (!HologramCMD.callModificationEvent(hologram, player, copied, HologramUpdateEvent.HologramModification.UPDATE_VISIBILITY_DISTANCE)) { + return false; + } + + if (Ints.compare(copied.getVisibilityDistance(), hologram.getData().getVisibilityDistance()) == 0) { + MessageHelper.warning(player, "This hologram already has this visibility distance"); + return false; + } + + hologram.getData().setVisibilityDistance(copied.getVisibilityDistance()); + + if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { + FancyHolograms.get().getHologramStorage().save(hologram); + } + + MessageHelper.success(player, "Changed visibility distance"); + return true; + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/hologram/version/HologramImpl.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/hologram/version/HologramImpl.java new file mode 100644 index 00000000..20325f56 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/hologram/version/HologramImpl.java @@ -0,0 +1,213 @@ +package de.oliver.fancyholograms.hologram.version; + +import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.events.HologramHideEvent; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancysitula.api.entities.*; +import de.oliver.fancysitula.factories.FancySitula; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; + +public final class HologramImpl extends Hologram { + + private FS_Display fsDisplay; + + public HologramImpl(@NotNull final HologramData data) { + super(data); + } + + @Override + public int getEntityId() { + return fsDisplay.getId(); + } + + @Override + public @Nullable org.bukkit.entity.Display getDisplayEntity() { + return null; + } + + @Override + public void create() { + final var location = data.getLocation(); + if (!location.isWorldLoaded()) { + return; + } + + switch (data.getType()) { + case TEXT -> this.fsDisplay = new FS_TextDisplay(); + case ITEM -> this.fsDisplay = new FS_ItemDisplay(); + case BLOCK -> this.fsDisplay = new FS_BlockDisplay(); + } + + if (data instanceof DisplayHologramData dd) { + fsDisplay.setTransformationInterpolationDuration(dd.getInterpolationDuration()); + fsDisplay.setTransformationInterpolationStartDeltaTicks(0); + } + + update(); + } + + @Override + public void delete() { + this.fsDisplay = null; + } + + @Override + public void update() { + if (fsDisplay == null) { + return; + } + + // location data + final var location = data.getLocation(); + if (location.getWorld() == null || !location.isWorldLoaded()) { + return; + } + fsDisplay.setLocation(location); + + if (fsDisplay instanceof FS_TextDisplay textDisplay && data instanceof TextHologramData textData) { + // line width + textDisplay.setLineWidth(Hologram.LINE_WIDTH); + + // background + final var background = textData.getBackground(); + if (background == null) { + textDisplay.setBackground(1073741824); // default background + } else if (background == Hologram.TRANSPARENT) { + textDisplay.setBackground(0); + } else { + textDisplay.setBackground(background.asARGB()); + } + + textDisplay.setStyleFlags((byte) 0); + textDisplay.setShadow(textData.hasTextShadow()); + textDisplay.setSeeThrough(textData.isSeeThrough()); + + switch (textData.getTextAlignment()) { + case LEFT -> textDisplay.setAlignLeft(true); + case RIGHT -> textDisplay.setAlignRight(true); + case CENTER -> { + textDisplay.setAlignLeft(false); + textDisplay.setAlignRight(false); + } + } + } else if (fsDisplay instanceof FS_ItemDisplay itemDisplay && data instanceof ItemHologramData itemData) { + // item + itemDisplay.setItem(itemData.getItemStack()); + } else if (fsDisplay instanceof FS_BlockDisplay blockDisplay && data instanceof BlockHologramData blockData) { + // block + +// BlockType blockType = RegistryAccess.registryAccess().getRegistry(RegistryKey.BLOCK).get(blockData.getBlock().getKey()); + blockDisplay.setBlock(blockData.getBlock().createBlockData().createBlockState()); + } + + if (data instanceof DisplayHologramData displayData) { + // billboard data + fsDisplay.setBillboard(FS_Display.Billboard.valueOf(displayData.getBillboard().name())); + + // brightness + if (displayData.getBrightness() != null) { + fsDisplay.setBrightnessOverride(displayData.getBrightness().getBlockLight() << 4 | displayData.getBrightness().getSkyLight() << 20); + } + + // entity transformation + fsDisplay.setTranslation(displayData.getTranslation()); + fsDisplay.setScale(displayData.getScale()); + fsDisplay.setLeftRotation(new Quaternionf()); + fsDisplay.setRightRotation(new Quaternionf()); + + // entity shadow + fsDisplay.setShadowRadius(displayData.getShadowRadius()); + fsDisplay.setShadowStrength(displayData.getShadowStrength()); + + fsDisplay.setViewRange(displayData.getVisibilityDistance()); + } + } + + + @Override + public boolean show(@NotNull final Player player) { + if (!new HologramShowEvent(this, player).callEvent()) { + return false; + } + + if (this.fsDisplay == null) { + create(); // try to create it if it doesn't exist every time + } + + if (fsDisplay == null) { + return false; // could not be created, nothing to show + } + + if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) { + return false; + } + + // TODO: cache player protocol version + // TODO: fix this +// final var protocolVersion = FancyHologramsPlugin.get().isUsingViaVersion() ? Via.getAPI().getPlayerVersion(player) : MINIMUM_PROTOCOL_VERSION; +// if (protocolVersion < MINIMUM_PROTOCOL_VERSION) { +// return false; +// } + + FS_RealPlayer fsPlayer = new FS_RealPlayer(player); + FancySitula.ENTITY_FACTORY.spawnEntityFor(fsPlayer, fsDisplay); + + this.viewers.add(player.getUniqueId()); + refreshHologram(player); + + return true; + } + + @Override + public boolean hide(@NotNull final Player player) { + if (!new HologramHideEvent(this, player).callEvent()) { + return false; + } + + if (fsDisplay == null) { + return false; // doesn't exist, nothing to hide + } + + FS_RealPlayer fsPlayer = new FS_RealPlayer(player); + FancySitula.ENTITY_FACTORY.despawnEntityFor(fsPlayer, fsDisplay); + + this.viewers.remove(player.getUniqueId()); + return true; + } + + + @Override + public void refresh(@NotNull final Player player) { + if (fsDisplay == null) { + return; // doesn't exist, nothing to refresh + } + + if (!isViewer(player)) { + return; + } + + FS_RealPlayer fsPlayer = new FS_RealPlayer(player); + + FancySitula.PACKET_FACTORY.createTeleportEntityPacket( + fsDisplay.getId(), + data.getLocation().x(), + data.getLocation().y(), + data.getLocation().z(), + data.getLocation().getYaw(), + data.getLocation().getPitch(), + true) + .send(fsPlayer); + + + if (fsDisplay instanceof FS_TextDisplay textDisplay) { + textDisplay.setText(getShownText(player)); + } + + FancySitula.ENTITY_FACTORY.setEntityDataFor(fsPlayer, fsDisplay); + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/BedrockPlayerListener.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/BedrockPlayerListener.java new file mode 100644 index 00000000..92e532ef --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/BedrockPlayerListener.java @@ -0,0 +1,22 @@ +package de.oliver.fancyholograms.listeners; + +import de.oliver.fancyholograms.FHFeatureFlags; +import de.oliver.fancyholograms.api.events.HologramShowEvent; +import de.oliver.fancyholograms.util.PluginUtils; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.geysermc.floodgate.api.FloodgateApi; + +public class BedrockPlayerListener implements Listener { + + @EventHandler + public void onHologramShow(final HologramShowEvent event) { + if (FHFeatureFlags.DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS.isEnabled() && PluginUtils.isFloodgateEnabled()) { + boolean isBedrockPlayer = FloodgateApi.getInstance().isFloodgatePlayer(event.getPlayer().getUniqueId()); + if (isBedrockPlayer) { + event.setCancelled(true); + } + } + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/NpcListener.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/NpcListener.java new file mode 100644 index 00000000..dc8ae301 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/NpcListener.java @@ -0,0 +1,58 @@ +package de.oliver.fancyholograms.listeners; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancylib.FancyLib; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancynpcs.api.events.NpcModifyEvent; +import de.oliver.fancynpcs.api.events.NpcRemoveEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +public final class NpcListener implements Listener { + + private final @NotNull FancyHolograms plugin; + + public NpcListener(@NotNull final FancyHolograms plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onRemove(@NotNull final NpcRemoveEvent event) { + this.plugin.getHologramsManager() + .getHolograms() + .stream() + .filter(hologram -> event.getNpc().getData().getName().equals(hologram.getData().getLinkedNpcName())) + .forEach(hologram -> hologram.getData().setLinkedNpcName(null)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onModify(@NotNull final NpcModifyEvent event) { + final var holograms = this.plugin.getHologramsManager().getHolograms(); + + switch (event.getModification()) { + case TYPE, LOCATION, SCALE -> { + final var needsToBeUpdated = holograms.stream() + .filter(hologram -> event.getNpc().getData().getName().equals(hologram.getData().getLinkedNpcName())) + .toList(); + + FancyLib.getInstance().getScheduler().runTaskLater(null, 1L, () -> needsToBeUpdated.forEach(this.plugin.getHologramsManager()::syncHologramWithNpc)); + } + case DISPLAY_NAME, SHOW_IN_TAB -> { + final var isLinked = holograms.stream() + .map(Hologram::getData) + .map(HologramData::getLinkedNpcName) + .anyMatch(event.getNpc().getData().getName()::equals); + + if (isLinked) { + event.setCancelled(true); + MessageHelper.error(event.getModifier(), "This modification is not allowed on a hologram linked npc"); + } + } + } + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/PlayerListener.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/PlayerListener.java new file mode 100644 index 00000000..fad939aa --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/PlayerListener.java @@ -0,0 +1,102 @@ +package de.oliver.fancyholograms.listeners; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.hologram.Hologram; +import net.kyori.adventure.resource.ResourcePackStatus; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; +import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +public final class PlayerListener implements Listener { + + private final @NotNull FancyHolograms plugin; + + private final Map> loadingResourcePacks; + + public PlayerListener(@NotNull final FancyHolograms plugin) { + this.plugin = plugin; + this.loadingResourcePacks = new HashMap<>(); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onJoin(@NotNull final PlayerJoinEvent event) { + for (final var hologram : this.plugin.getHologramsManager().getHolograms()) { + hologram.updateShownStateFor(event.getPlayer()); + } + + if (!this.plugin.getHologramConfiguration().areVersionNotificationsMuted() && event.getPlayer().hasPermission("fancyholograms.admin")) { + FancyHolograms.get().getHologramThread().submit(() -> FancyHolograms.get().getVersionConfig().checkVersionAndDisplay(event.getPlayer(), true)); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onQuit(@NotNull final PlayerQuitEvent event) { + FancyHolograms.get().getHologramThread().submit(() -> { + for (final var hologram : this.plugin.getHologramsManager().getHolograms()) { + hologram.hideHologram(event.getPlayer()); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onTeleport(@NotNull final PlayerTeleportEvent event) { + for (final Hologram hologram : this.plugin.getHologramsManager().getHolograms()) { + hologram.updateShownStateFor(event.getPlayer()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onWorldChange(@NotNull final PlayerChangedWorldEvent event) { + for (final Hologram hologram : this.plugin.getHologramsManager().getHolograms()) { + hologram.updateShownStateFor(event.getPlayer()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onResourcePackStatus(@NotNull final PlayerResourcePackStatusEvent event) { + // Skipping event calls before player has fully loaded to the server. + // This should fix NPE due to vanillaPlayer.connection being null when sending resource-packs in the configuration stage. + if (!event.getPlayer().isOnline()) + return; + final UUID playerUniqueId = event.getPlayer().getUniqueId(); + final UUID packUniqueId = getResourcePackID(event); + // Adding accepted resource-pack to the list of currently loading resource-packs for that player. + if (event.getStatus() == Status.ACCEPTED) + loadingResourcePacks.computeIfAbsent(playerUniqueId, (___) -> new ArrayList<>()).add(packUniqueId); + // Once successfully loaded (or failed to download), removing resource-pack from the map. + else if (event.getStatus() == Status.SUCCESSFULLY_LOADED || event.getStatus() == Status.FAILED_DOWNLOAD) { + loadingResourcePacks.computeIfAbsent(playerUniqueId, (___) -> new ArrayList<>()).removeIf(uuid -> uuid.equals(packUniqueId)); + // Refreshing holograms once (possibly) all resource-packs are loaded. + if (loadingResourcePacks.get(playerUniqueId) != null && loadingResourcePacks.get(playerUniqueId).isEmpty()) { + // Removing player from the map, as they're no longer needed here. + loadingResourcePacks.remove(playerUniqueId); + // Refreshing holograms as to make sure custom textures are loaded. + for (final Hologram hologram : this.plugin.getHologramsManager().getHolograms()) { + hologram.refreshHologram(event.getPlayer()); + } + } + } + } + + // For 1.20.2 and higher this method returns actual pack identifier, while for older versions, the identifier is a dummy UUID full of zeroes. + // Versions prior 1.20.2 supports sending and receiving only one resource-pack and a dummy, constant identifier can be used as a key. + private static @NotNull UUID getResourcePackID(final @NotNull PlayerResourcePackStatusEvent event) { + try { + event.getClass().getMethod("getID"); + return event.getID(); + } catch (final @NotNull NoSuchMethodException e) { + return new UUID(0,0); + } + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/WorldListener.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/WorldListener.java new file mode 100644 index 00000000..84d55edf --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/listeners/WorldListener.java @@ -0,0 +1,27 @@ +package de.oliver.fancyholograms.listeners; + +import de.oliver.fancyholograms.FancyHolograms; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +public class WorldListener implements Listener { + + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + FancyHolograms.get().getHologramThread().submit(() -> { + FancyHolograms.get().getFancyLogger().info("Loading holograms for world " + event.getWorld().getName()); + FancyHolograms.get().getHologramsManager().loadHolograms(event.getWorld().getName()); + }); + } + + @EventHandler + public void onWorldUnload(WorldUnloadEvent event) { + FancyHolograms.get().getHologramThread().submit(() -> { + FancyHolograms.get().getFancyLogger().info("Unloading holograms for world " + event.getWorld().getName()); + FancyHolograms.get().getHologramsManager().unloadHolograms(event.getWorld().getName()); + }); + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsBootstrapper.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsBootstrapper.java new file mode 100644 index 00000000..e6ee815e --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsBootstrapper.java @@ -0,0 +1,19 @@ +package de.oliver.fancyholograms.loaders; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.plugin.bootstrap.PluginProviderContext; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +public class FancyHologramsBootstrapper implements PluginBootstrap { + @Override + public void bootstrap(@NotNull BootstrapContext bootstrapContext) { + + } + + @Override + public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) { + return PluginBootstrap.super.createPlugin(context); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsLoader.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsLoader.java new file mode 100644 index 00000000..a4c0c716 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/loaders/FancyHologramsLoader.java @@ -0,0 +1,12 @@ +package de.oliver.fancyholograms.loaders; + +import io.papermc.paper.plugin.loader.PluginClasspathBuilder; +import io.papermc.paper.plugin.loader.PluginLoader; +import org.jetbrains.annotations.NotNull; + +public class FancyHologramsLoader implements PluginLoader { + @Override + public void classloader(@NotNull PluginClasspathBuilder pluginClasspathBuilder) { + + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java new file mode 100644 index 00000000..aad9ecf7 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java @@ -0,0 +1,224 @@ +package de.oliver.fancyholograms.storage; + +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.HologramStorage; +import de.oliver.fancyholograms.api.data.BlockHologramData; +import de.oliver.fancyholograms.api.data.DisplayHologramData; +import de.oliver.fancyholograms.api.data.ItemHologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancyholograms.api.hologram.Hologram; +import de.oliver.fancyholograms.api.hologram.HologramType; +import org.bukkit.Location; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class FlatFileHologramStorage implements HologramStorage { + + private static final ReadWriteLock lock = new ReentrantReadWriteLock(); + private static final File HOLOGRAMS_CONFIG_FILE = new File("plugins/FancyHolograms/holograms.yml"); + + @Override + public void saveBatch(Collection holograms, boolean override) { + lock.readLock().lock(); + + boolean success = false; + YamlConfiguration config = null; + try { + config = YamlConfiguration.loadConfiguration(HOLOGRAMS_CONFIG_FILE); + + if (override) { + config.set("holograms", null); + } + + for (final var hologram : holograms) { + writeHologram(config, hologram); + } + + success = true; + } finally { + lock.readLock().unlock(); + if (success) { + saveConfig(config); + } + } + + FancyHolograms.get().getFancyLogger().debug("Saved " + holograms.size() + " holograms to file (override=" + override + ")"); + } + + @Override + public void save(Hologram hologram) { + lock.readLock().lock(); + + boolean success = false; + YamlConfiguration config = null; + try { + config = YamlConfiguration.loadConfiguration(HOLOGRAMS_CONFIG_FILE); + writeHologram(config, hologram); + + success = true; + } finally { + lock.readLock().unlock(); + if (success) { + saveConfig(config); + } + } + + FancyHolograms.get().getFancyLogger().debug("Saved hologram " + hologram.getData().getName() + " to file"); + } + + @Override + public void delete(Hologram hologram) { + lock.readLock().lock(); + + boolean success = false; + YamlConfiguration config = null; + try { + config = YamlConfiguration.loadConfiguration(HOLOGRAMS_CONFIG_FILE); + config.set("holograms." + hologram.getData().getName(), null); + + success = true; + } finally { + lock.readLock().unlock(); + if (success) { + saveConfig(config); + } + } + + FancyHolograms.get().getFancyLogger().debug("Deleted hologram " + hologram.getData().getName() + " from file"); + } + + @Override + public Collection loadAll() { + List holograms = readHolograms(FlatFileHologramStorage.HOLOGRAMS_CONFIG_FILE, null); + FancyHolograms.get().getFancyLogger().debug("Loaded " + holograms.size() + " holograms from file"); + return holograms; + } + + @Override + public Collection loadAll(String world) { + List holograms = readHolograms(FlatFileHologramStorage.HOLOGRAMS_CONFIG_FILE, world); + FancyHolograms.get().getFancyLogger().debug("Loaded " + holograms.size() + " holograms from file (world=" + world + ")"); + return holograms; + } + + /** + * @param world The world to load the holograms from. (null for all worlds) + */ + private List readHolograms(@NotNull File configFile, @Nullable String world) { + lock.readLock().lock(); + try { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + if (!config.isConfigurationSection("holograms")) { + FancyHolograms.get().getFancyLogger().warn("No holograms section found in config"); + return new ArrayList<>(0); + } + + int configVersion = config.getInt("version", 1); + if (configVersion != 2) { + FancyHolograms.get().getFancyLogger().warn("Config version is not 2, skipping loading holograms"); + FancyHolograms.get().getFancyLogger().warn("Old config version detected, skipping loading holograms"); + return new ArrayList<>(0); + } + + List holograms = new ArrayList<>(); + + ConfigurationSection hologramsSection = config.getConfigurationSection("holograms"); + for (String name : hologramsSection.getKeys(false)) { + ConfigurationSection holoSection = hologramsSection.getConfigurationSection(name); + if (holoSection == null) { + FancyHolograms.get().getFancyLogger().warn("Could not load hologram section in config"); + continue; + } + + if (world != null && !holoSection.getString("location.world").equals(world)) { + continue; + } + + String typeName = holoSection.getString("type"); + if (typeName == null) { + FancyHolograms.get().getFancyLogger().warn("HologramType was not saved"); + continue; + } + + HologramType type = HologramType.getByName(typeName); + if (type == null) { + FancyHolograms.get().getFancyLogger().warn("Could not parse HologramType"); + continue; + } + + DisplayHologramData displayData = null; + switch (type) { + case TEXT -> displayData = new TextHologramData(name, new Location(null, 0, 0, 0)); + case ITEM -> displayData = new ItemHologramData(name, new Location(null, 0, 0, 0)); + case BLOCK -> displayData = new BlockHologramData(name, new Location(null, 0, 0, 0)); + } + + if (!displayData.read(holoSection, name)) { + FancyHolograms.get().getFancyLogger().warn("Could not read hologram data - skipping hologram"); + continue; + } + + Hologram hologram = FancyHolograms.get().getHologramManager().create(displayData); + holograms.add(hologram); + } + + FancyHolograms.get().getFancyLogger().debug("Loaded " + holograms.size() + " holograms from file"); + return holograms; + } finally { + lock.readLock().unlock(); + } + } + + private void writeHologram(YamlConfiguration config, Hologram hologram) { + @NotNull ConfigurationSection section; + if (!config.isConfigurationSection("holograms")) { + section = config.createSection("holograms"); + } else { + section = Objects.requireNonNull(config.getConfigurationSection("holograms")); + } + + String holoName = hologram.getData().getName(); + + ConfigurationSection holoSection = section.getConfigurationSection(holoName); + if (holoSection == null) { + holoSection = section.createSection(holoName); + } + + hologram.getData().write(holoSection, holoName); + FancyHolograms.get().getFancyLogger().debug("Wrote hologram " + holoName + " to config"); + } + + private void saveConfig(YamlConfiguration config) { + config.set("version", 2); + config.setInlineComments("version", List.of("DO NOT CHANGE")); + + FancyHolograms.get().getFileStorageExecutor().execute(() -> { + lock.writeLock().lock(); + try { + config.save(HOLOGRAMS_CONFIG_FILE); + } catch (IOException e) { + e.printStackTrace(); + } finally { + lock.writeLock().unlock(); + } + + if(!FancyHolograms.canGet()) { + return; + } + + FancyHolograms.get().getFancyLogger().debug("Saved config to file"); + }); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/ConverterTarget.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/ConverterTarget.java new file mode 100644 index 00000000..3fd91912 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/ConverterTarget.java @@ -0,0 +1,57 @@ +package de.oliver.fancyholograms.storage.converter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Pattern; + +public class ConverterTarget { + private final @NotNull Pattern hologramIdRegex; + + public ConverterTarget(@NotNull Pattern matching) { + this.hologramIdRegex = matching; + } + + public @NotNull Pattern getRegex() { + return hologramIdRegex; + } + + public boolean matches(@NotNull String hologramId) { + return hologramIdRegex.asMatchPredicate().test(hologramId); + } + + private static final ConverterTarget ALL = new ConverterTarget(Pattern.compile(".*")); + public static @NotNull ConverterTarget all() { + return ALL; + } + + public static @NotNull ConverterTarget ofAll(@NotNull String first, @NotNull String... others) { + StringBuilder builder = new StringBuilder(first); + + if (others.length > 0) { + builder.append("|"); + } + + builder.append(String.join("|", others)); + + return new ConverterTarget(Pattern.compile(builder.toString())); + } + + public static @NotNull ConverterTarget ofSingle(@NotNull String match) { + return new ConverterTarget(Pattern.compile(match)); + } + + public static @Nullable ConverterTarget ofStringNullable(@NotNull String match) { + + if (match.equalsIgnoreCase("*")) { + return all(); + } + + try { + return new ConverterTarget(Pattern.compile(match)); + } catch (Exception ignored) { + return null; + } + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/DecentHologramsConverter.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/DecentHologramsConverter.java new file mode 100644 index 00000000..21a36b4b --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/DecentHologramsConverter.java @@ -0,0 +1,256 @@ +package de.oliver.fancyholograms.storage.converter; + +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancyholograms.api.data.ItemHologramData; +import de.oliver.fancyholograms.api.data.TextHologramData; +import de.oliver.fancylib.MessageHelper; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Display; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.io.File; +import java.util.*; + +public class DecentHologramsConverter extends HologramConverter { + private static final float VANILLA_PIXEL_BLOCK_SIZE = 0.0625f; + private static final float TEXT_DISPLAY_PIXEL = VANILLA_PIXEL_BLOCK_SIZE / 3; + private static final float TEXT_DISPLAY_LINE_HEIGHT = TEXT_DISPLAY_PIXEL * 14; + private static final String PROCESS_ICONS_FLAG = "--processIcons"; + private static final String ICON_PREFIX = "#ICON: "; + private static final File DECENT_HOLOGRAMS_DATA = new File("./plugins/DecentHolograms/holograms/"); + + @Override + public @NotNull String getId() { + return "DecentHolograms"; + } + + @Override + public boolean canRunConverter() { + return DECENT_HOLOGRAMS_DATA.exists(); + } + + @Override + public @NotNull List convertHolograms(@NotNull HologramConversionSession spec) { + boolean processIcons = Arrays.stream(spec.getAdditionalArguments()).anyMatch((arg) -> arg.equalsIgnoreCase(PROCESS_ICONS_FLAG)); + + if (processIcons) { + MessageHelper.warning( + spec.getInvoker(), + "--processIcons argument is experimental and may produce unexpected results." + ); + } else { + MessageHelper.info( + spec.getInvoker(), + "Any lines containing an #ICON will be removed. You may run with --processIcons to attempt conversion, but this is experimental." + ); + } + + final List targetHolograms = getConvertableHolograms() + .stream() + .filter((id) -> spec.getTarget().matches(id)) + .toList(); + + if (targetHolograms.isEmpty()) { + throw new RuntimeException("The provided target matches no holograms."); + } + + ArrayList converted = new ArrayList<>(); + + for (final String id : targetHolograms) { + final List results = convert(id, processIcons); + + if (results.isEmpty()) { + spec.logUnsuccessfulConversion(id, "Unable to convert this hologram, there is no convertable content."); + } else { + spec.logSuccessfulConversion(id, results); + } + + converted.addAll(results); + } + + return converted; + } + + @Override + public @NotNull List getConvertableHolograms() { + final File[] files = DECENT_HOLOGRAMS_DATA.listFiles(); + + if (files == null || files.length == 0) { + return Collections.emptyList(); + } + + return Arrays.stream(files) + .map((file) -> file.getName().replace(".yml", "")) + .toList(); + } + + private @NotNull List convert(@NotNull String hologramId, boolean processIcons) { + final File file = DECENT_HOLOGRAMS_DATA.toPath() + .resolve(hologramId.endsWith(".yml") ? hologramId : hologramId + ".yml") + .toFile(); + + if (!file.exists() || !file.canRead()) { + throw new RuntimeException("File does not exist or is not readable."); + } + + FileConfiguration data = YamlConfiguration.loadConfiguration(file); + Objects.requireNonNull(data, "No data could be read from the DecentHolograms file!"); + + final Location location = parseLocation(data.getString("location")); + final double displayRange = data.getDouble("display-range"); + final int updateInterval = data.getInt("update-interval"); + + // TODO handle exceptions here + final Object firstPage = data.getMapList("pages") + .stream() + .findFirst() + .orElseThrow(() -> new RuntimeException(String.format("There are no pages for %s!", hologramId))) + .get("lines"); + + Objects.requireNonNull(firstPage, String.format("There is no first page for %s!", hologramId)); + + final List> firstPageSections; + + try { + firstPageSections = (List>) firstPage; + } catch (ClassCastException ignored) { + throw new RuntimeException(String.format("The first page for %s is invalid!", hologramId)); + } + + List lines = firstPageSections + .stream() + .map((line) -> (String) line.get("content")) + .toList(); + + if (!processIcons) { + lines = lines.stream() + .map((line) -> line.startsWith(ICON_PREFIX) ? "" : line) + .toList(); + } + + final TextHologramData hologram = new TextHologramData(hologramId, location); + + hologram.setText(lines); + hologram.setTextShadow(true); + hologram.setTextUpdateInterval(updateInterval); + hologram.setVisibilityDistance((int) displayRange); + hologram.setBillboard(Display.Billboard.VERTICAL); + hologram.setPersistent(true); + + List results = new ArrayList<>(); + if (processIcons) { + results.addAll(convertSplitLines(hologram, firstPageSections)); + } else { + results.add(hologram); + } + + return results; + } + + /** + * Attempts to convert #ICON prefixed lines into item displays. + *

+ * This is done off some arbitrary values I found when testing + * on another project, and might not be 100% accurate. However, + * it should be enough to give users an idea of what it would look + * like. + * + * @author MattMX + * @param base The root hologram (background) + * @param lines lines from the DecentHolograms hologram's first page. + * @return A list of created [HologramData] children. + */ + private @NotNull List convertSplitLines(@NotNull TextHologramData base, @NotNull List> lines) { + final List stack = new ArrayList<>(); + final List finalBaseLines = new ArrayList<>(); + stack.add(base); + + int subTypes = 0; + float currentYOffset = 0f; + + // Track total height of hologram apx (hologram y is inverted) + float totalHeight = 0f; + + for (final Map entry : lines) { + final Object contentEntry = entry.get("content"); + // TODO add height + + if (!(contentEntry instanceof String line)) continue; + + if (line.startsWith(ICON_PREFIX)) { + final String materialTypeString = line.replace(ICON_PREFIX, ""); + final Material material = Material.valueOf(materialTypeString); + + final String formattedId = String.format("%s_icon_%s", base.getName(), subTypes++); + final ItemHologramData data = new ItemHologramData(formattedId, base.getLocation()); + + data.setItemStack(new ItemStack(material)); + data.setBillboard(Display.Billboard.VERTICAL); + data.setScale(new Vector3f(0.45f, 0.45f, 0.45f)); + data.setTranslation(new Vector3f(0f, currentYOffset, 0f)); + data.setVisibilityDistance(base.getVisibilityDistance()); + data.setPersistent(true); + + float h = TEXT_DISPLAY_LINE_HEIGHT + 0.12f; + + currentYOffset += h; + totalHeight += h; + + // Empty space for text + // TODO find average item height for now 0.12f seems ok when scale is 0.45f + finalBaseLines.addAll(List.of("&r", "&r")); + stack.add(data); + } else { + // Empty line + finalBaseLines.add(line); + float h = TEXT_DISPLAY_LINE_HEIGHT; + + currentYOffset += h; + totalHeight += h; + } + } + + base.setText(finalBaseLines); + + // Now invert their y offset + for (@NotNull HologramData holo : stack) { + if (holo instanceof ItemHologramData itemHolo) { + itemHolo.setTranslation( + new Vector3f( + itemHolo.getTranslation().x, + totalHeight - itemHolo.getTranslation().y - 0.25f, + itemHolo.getTranslation().z + ) + ); + } + } + + return stack; + } + + private @NotNull Location parseLocation(@Nullable String location) { + Objects.requireNonNull(location, "Location cannot be empty!"); + + final String[] split = location.split(":"); + + if (split.length != 4) { + throw new IllegalStateException(String.format("Location in %s didn't have 4 arguments (split by :)", location)); + } + + World world = Objects.requireNonNull(Bukkit.getWorld(split[0]), String.format("World does not exist for location %s", location)); + + double x = Double.parseDouble(split[1]); + double y = Double.parseDouble(split[2]); + double z = Double.parseDouble(split[3]); + + return new Location(world, x, y, z); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/FHConversionRegistry.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/FHConversionRegistry.java new file mode 100644 index 00000000..366d4773 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/FHConversionRegistry.java @@ -0,0 +1,63 @@ +package de.oliver.fancyholograms.storage.converter; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +public class FHConversionRegistry { + private static final Map converters = new HashMap<>(); + + public static void registerBuiltInConverters() { + register(new DecentHologramsConverter()); + } + + public static boolean register(HologramConverter converter) { + return converters.putIfAbsent(converter.getId(), converter) != null; + } + + public static @NotNull Optional getConverterById(@NotNull String id) { + return Optional.ofNullable(converters.get(id)); + } + + public static @NotNull Optional getConverter(@NotNull String id) { + return getConverterById(id) + .map((converter) -> { + try { + return (T) converter; + } catch (ClassCastException ignored) { + return null; + } + }); + } + + public static @NotNull Optional getConverter(@NotNull Class clazz) { + return converters.values() + .stream() + .filter(clazz::isInstance) + .findFirst() + .map((converter) -> { + try { + return (T) converter; + } catch (ClassCastException ignored) { + return null; + } + }); + } + + public static @NotNull Set getAllConverterIds() { + return converters.keySet(); + } + + public static @NotNull Set getAllUsableConverterIds() { + return converters + .entrySet() + .stream() + .filter((entry) -> entry.getValue().canRunConverter()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConversionSession.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConversionSession.java new file mode 100644 index 00000000..0c32739f --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConversionSession.java @@ -0,0 +1,80 @@ +package de.oliver.fancyholograms.storage.converter; + +import de.oliver.fancyholograms.api.data.HologramData; +import de.oliver.fancylib.MessageHelper; +import de.oliver.fancylib.translations.message.Message; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class HologramConversionSession { + private final @NotNull ConverterTarget target; + private final @NotNull CommandSender invoker; + private final String[] arguments; + + public HologramConversionSession( + @NotNull ConverterTarget target + ) { + this(target, Bukkit.getConsoleSender(), new String[0]); + } + + public HologramConversionSession( + @NotNull ConverterTarget target, + @NotNull CommandSender invoker, + @NotNull String[] arguments + ) { + this.target = target; + this.invoker = invoker; + this.arguments = arguments; + } + + public @NotNull ConverterTarget getTarget() { + return this.target; + } + + public @NotNull CommandSender getInvoker() { + return this.invoker; + } + + public @NotNull String[] getAdditionalArguments() { + return this.arguments; + } + + public void logUnsuccessfulConversion(@NotNull String oldHologram, @Nullable String message) { + if (message != null) { + MessageHelper.error( + getInvoker(), + String.format("There was an issue converting %s: %s", oldHologram, message) + ); + } else { + MessageHelper.error( + getInvoker(), + String.format("There was an issue converting %s!", oldHologram) + ); + } + } + + public void logSuccessfulConversion(@NotNull String oldHologram, @NotNull HologramData result) { + logSuccessfulConversion(oldHologram, List.of(result)); + } + + public void logSuccessfulConversion(@NotNull String oldHologram, @NotNull List results) { + MessageHelper.info( + getInvoker(), + String.format("Successfully converted %s to %s hologram(s).", oldHologram, results.size()) + ); + + for (@NotNull HologramData data : results) { + MessageHelper.info( + getInvoker(), + String.format(" - %s type: %s", data.getName(), data.getType().name()) + ); + } + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConverter.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConverter.java new file mode 100644 index 00000000..30e2c115 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/storage/converter/HologramConverter.java @@ -0,0 +1,44 @@ +package de.oliver.fancyholograms.storage.converter; + +import de.oliver.fancyanalytics.api.events.Event; +import de.oliver.fancyholograms.FancyHolograms; +import de.oliver.fancyholograms.api.data.HologramData; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public abstract class HologramConverter { + + public abstract @NotNull String getId(); + + public abstract boolean canRunConverter(); + + /** + * Returns a list of converted holograms + * @param spec Configuration of the hologram conversion + * @return A list of converted holograms. + */ + protected abstract @NotNull List convertHolograms(@NotNull HologramConversionSession spec); + + + /** + * Returns a list of converted holograms + * @param spec Configuration of the hologram conversion + * @return A list of converted holograms. + */ + public final @NotNull List convert(@NotNull HologramConversionSession spec) { + List converted = convertHolograms(spec); + + Event event = new Event("HologramsConverted") + .withProperty("converter", getId()) + .withProperty("target", spec.getTarget().getRegex().pattern()) + .withProperty("amount", String.valueOf(converted.size())); + FancyHolograms.get().getFancyAnalytics().sendEvent(event); + + return converted; + } + + public @NotNull List getConvertableHolograms() { + return List.of(); + } +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/Constants.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/Constants.java new file mode 100644 index 00000000..6e62d967 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/Constants.java @@ -0,0 +1,52 @@ +package de.oliver.fancyholograms.util; + +import de.oliver.fancylib.MessageHelper; + +import java.text.DecimalFormat; + +public enum Constants { + ; + + public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#########.##"); + + public final static DecimalFormat COORDINATES_DECIMAL_FORMAT = new DecimalFormat("#########.##"); + + public static final String FH_COMMAND_USAGE = "/fancyholograms "; + + public static final String HELP_TEXT = """ + <%primary_color%>FancyHolograms commands help: + <%primary_color%>- /hologram help - Shows all (sub)commands + <%primary_color%>- /hologram list - Shows you a overview of all holograms + <%primary_color%>- /hologram nearby - Shows all holograms nearby you in a range + <%primary_color%>- /hologram teleport - Teleports you to a hologram + <%primary_color%>- /hologram create - Creates a new hologram + <%primary_color%>- /hologram remove - Removes a hologram + <%primary_color%>- /hologram copy - Copies a hologram + <%primary_color%>- /hologram edit addLine - Adds a line at the bottom + <%primary_color%>- /hologram edit removeLine - Removes a line at the bottom + <%primary_color%>- /hologram edit insertBefore - Inserts a line before another + <%primary_color%>- /hologram edit insertAfter - Inserts a line after another + <%primary_color%>- /hologram edit setLine - Edits the line + <%primary_color%>- /hologram edit position - Teleports the hologram to you + <%primary_color%>- /hologram edit moveTo [yaw] [pitch] - Teleports the hologram to the coordinates + <%primary_color%>- /hologram edit rotate - Rotates the hologram + <%primary_color%>- /hologram edit scale - Changes the scale of the hologram + <%primary_color%>- /hologram edit billboard - Changes the billboard of the hologram + <%primary_color%>- /hologram edit background - Changes the background of the hologram + <%primary_color%>- /hologram edit textShadow - Enables/disables the text shadow + <%primary_color%>- /hologram edit textAlignment - Sets the text alignment + <%primary_color%>- /hologram edit seeThrough - Enables/disables whether the text can be seen through blocks + <%primary_color%>- /hologram edit shadowRadius - Changes the shadow radius of the hologram + <%primary_color%>- /hologram edit shadowStrength - Changes the shadow strength of the hologram + <%primary_color%>- /hologram edit brightness <0-15> - Changes the brightness of the hologram + <%primary_color%>- /hologram edit updateTextInterval - Sets the interval for updating the text + """.replace("%primary_color%", MessageHelper.getPrimaryColor()); + + public static final String HELP_TEXT_NPCS = """ + <%primary_color%>- /hologram edit linkWithNpc - Links the hologram with an NPC + <%primary_color%>- /hologram edit unlinkWithNpc - Unlinks the hologram with an NPC + """.replace("%primary_color%", MessageHelper.getPrimaryColor()); + + public static final String INVALID_NEARBY_RANGE = "Provide an integer radius to search for holograms nearby."; + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/NumberHelper.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/NumberHelper.java new file mode 100644 index 00000000..1e19728c --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/NumberHelper.java @@ -0,0 +1,15 @@ +package de.oliver.fancyholograms.util; + +import java.util.Optional; + +public class NumberHelper { + + public static Optional parseInt(String toParse) { + try { + return Optional.of(Integer.parseInt(toParse)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + +} diff --git a/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/PluginUtils.java b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/PluginUtils.java new file mode 100644 index 00000000..3ec815ed --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/java/de/oliver/fancyholograms/util/PluginUtils.java @@ -0,0 +1,18 @@ +package de.oliver.fancyholograms.util; + +import org.bukkit.Bukkit; + +public class PluginUtils { + + public static boolean isFancyNpcsEnabled() { + return Bukkit.getPluginManager().getPlugin("FancyNpcs") != null; + } + + public static boolean isFloodgateEnabled() { + return Bukkit.getPluginManager().getPlugin("floodgate") != null; + } + + public static boolean isViaVersionEnabled() { + return Bukkit.getPluginManager().getPlugin("ViaVersion") != null; + } +} diff --git a/plugins/fancyholograms-v2/src/main/resources/version.yml b/plugins/fancyholograms-v2/src/main/resources/version.yml new file mode 100644 index 00000000..2de69789 --- /dev/null +++ b/plugins/fancyholograms-v2/src/main/resources/version.yml @@ -0,0 +1,3 @@ +version: $version +build: $build +hash: $hash \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index df52045f..db20ee37 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,12 +1,6 @@ rootProject.name = "minecraft-plugins" -include(":plugins:fancyholograms") -include(":plugins:fancyholograms:api") -include(":plugins:fancyholograms:implementation_1_20_4") -include(":plugins:fancyholograms:implementation_1_20_2") -include(":plugins:fancyholograms:implementation_1_20_1") -include(":plugins:fancyholograms:implementation_1_19_4") - +include(":plugins:fancynpcs:") include(":plugins:fancynpcs:api") include(":plugins:fancynpcs:implementation_1_21_5") include(":plugins:fancynpcs:implementation_1_21_4") @@ -19,6 +13,19 @@ include(":plugins:fancynpcs:implementation_1_20_1") include(":plugins:fancynpcs:implementation_1_20") include(":plugins:fancynpcs:implementation_1_19_4") +include(":plugins:fancyholograms-v2") +include(":plugins:fancyholograms-v2:api") +include(":plugins:fancyholograms-v2:implementation_1_20_4") +include(":plugins:fancyholograms-v2:implementation_1_20_2") +include(":plugins:fancyholograms-v2:implementation_1_20_1") +include(":plugins:fancyholograms-v2:implementation_1_19_4") + +include(":plugins:fancyholograms") +include(":plugins:fancyholograms:api") +include(":plugins:fancyholograms:implementation_1_20_4") +include(":plugins:fancyholograms:implementation_1_20_2") +include(":plugins:fancyholograms:implementation_1_20_1") +include(":plugins:fancyholograms:implementation_1_19_4") include(":plugins:fancyvisuals") @@ -39,4 +46,10 @@ include(":libraries:packets:test_plugin") include(":tools:deployment") -include(":tools:quick-e2e") \ No newline at end of file +include(":tools:quick-e2e") + +pluginManagement { + repositories { + gradlePluginPortal() + } +} \ No newline at end of file