Add FancyHolograms

This commit is contained in:
Oliver
2025-03-03 20:59:48 +01:00
parent e3a68e337e
commit 1c8a53e778
136 changed files with 10099 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
**/.idea/
**/build/
**/run/
**/.gradle/
docs/output/
backend/src/main/resources/frontend
backend/out
logger/out
sdks/java-sdk/out

1
README.md Normal file
View File

@@ -0,0 +1 @@
# FancyInnovations | Minecraft Plugins

12
build.gradle.kts Normal file
View File

@@ -0,0 +1,12 @@
allprojects {
group = "de.oliver"
description = "Minecraft plugins of FancyInnovations"
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.fancyplugins.de/releases")
maven(url = "https://jitpack.io")
}
}

123
plugins/fancyholograms/.gitignore vendored Normal file
View File

@@ -0,0 +1,123 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
/.idea/
# Eclipse
.classpath
.project
.settings/
plugin/bin/
api/bin/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/
build
.gradle
/*.jar

View File

@@ -0,0 +1,145 @@
<div align="center">
![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)
<br />
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.
</div>
## 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.
<br />
## 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)**
<br />
## 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.
<br />
## 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
<repository>
<id>fancyplugins-releases</id>
<name>FancyPlugins Repository</name>
<url>https://repo.fancyplugins.de/releases</url>
</repository>
```
```xml
<dependency>
<groupId>de.oliver</groupId>
<artifactId>FancyHolograms</artifactId>
<version>[VERSION]</version>
<scope>provided</scope>
</dependency>
```
### Gradle
```groovy
repositories {
maven("https://repo.fancyplugins.de/releases")
}
dependencies {
compileOnly("de.oliver:FancyHolograms:[VERSION]")
}
```
<br />
## 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.
```
<br />
## 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)
<sup>Provided by [@OliverSchlueter](https://github.com/OliverSchlueter)</sup>
![Screenshot 2](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example2.jpeg?raw=true)
<sup>Provided by [@OliverSchlueter](https://github.com/OliverSchlueter)</sup>
![Screenshot 3](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example3.jpeg?raw=true)
<sup>Provided by [@OliverSchlueter](https://github.com/OliverSchlueter)</sup>
![Screenshot 4](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example4.jpeg?raw=true)
<sup>Provided by [@OliverSchlueter](https://github.com/OliverSchlueter)</sup>
![Screenshot 5](https://github.com/FancyMcPlugins/FancyHolograms/blob/main/images/screenshots/example5.jpeg?raw=true)
<sup>Provided by [@OliverSchlueter](https://github.com/OliverSchlueter)</sup>

View File

@@ -0,0 +1,71 @@
plugins {
id("java-library")
id("maven-publish")
id("com.gradleup.shadow") version "8.3.6"
}
val minecraftVersion = "1.19.4"
dependencies {
compileOnly("io.papermc.paper:paper-api:$minecraftVersion-R0.1-SNAPSHOT")
compileOnly("de.oliver:FancyLib:35")
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<BasicAuthentication>("basic")
}
}
maven {
name = "fancypluginsSnapshots"
url = uri("https://repo.fancyplugins.de/snapshots")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = rootProject.group.toString()
artifactId = rootProject.name
version = rootProject.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)
}
}

View File

@@ -0,0 +1,75 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.api.trait.HologramTraitRegistry;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Function;
public interface FancyHolograms {
static FancyHolograms get() {
if (isEnabled()) {
return EnabledChecker.getPlugin();
}
throw new NullPointerException("Plugin is not enabled");
}
static boolean isEnabled() {
return EnabledChecker.isFancyHologramsEnabled();
}
JavaPlugin getPlugin();
ExtendedFancyLogger getFancyLogger();
HologramConfiguration getHologramConfiguration();
@ApiStatus.Internal
Function<HologramData, Hologram> getHologramFactory();
ScheduledExecutorService getHologramThread();
HologramRegistry getRegistry();
HologramController getController();
@ApiStatus.Experimental
HologramTraitRegistry getTraitRegistry();
class EnabledChecker {
private static Boolean enabled;
private static FancyHolograms plugin;
public static Boolean isFancyHologramsEnabled() {
if (enabled != null) return enabled;
Plugin pl = Bukkit.getPluginManager().getPlugin("FancyHolograms");
if (pl != null && pl.isEnabled()) {
try {
plugin = (FancyHolograms) 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 FancyHolograms getPlugin() {
return plugin;
}
}
}

View File

@@ -0,0 +1,62 @@
package de.oliver.fancyholograms.api;
import org.jetbrains.annotations.NotNull;
public interface HologramConfiguration {
/**
* Reloads the configuration.
*
* @param plugin The plugin instance.
*/
void reload(@NotNull FancyHolograms 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();
}

View File

@@ -0,0 +1,43 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* The controller for holograms, responsible for showing and hiding them to players.
*/
public interface HologramController {
/**
* Shows the hologram to the given players, if they should see it, and it is not already shown to them.
*/
@ApiStatus.Internal
void showHologramTo(@NotNull final Hologram hologram, @NotNull final Player ...players);
/**
* Hides the hologram from the given players, if they should not see it, and it is shown to them.
*/
@ApiStatus.Internal
void hideHologramFrom(@NotNull final Hologram hologram, @NotNull final Player ...players);
/**
* Returns whether the given player should see the hologram.
*/
@ApiStatus.Internal
boolean shouldSeeHologram(@NotNull final Hologram hologram, @NotNull final Player player);
/**
* Spawns the hologram to the given players, if they should see it, and it is not already shown to them.
* Hide the hologram from the players that should not see it.
*/
void refreshHologram(@NotNull final Hologram hologram, @NotNull final Player ...players);
default void refreshHologram(@NotNull final Hologram hologram, @NotNull final Collection<? extends Player> players) {
refreshHologram(hologram, players.toArray(new Player[0]));
}
}

View File

@@ -0,0 +1,74 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyholograms.api.hologram.Hologram;
import java.util.Collection;
import java.util.Optional;
/**
* An interface for managing the registration and retrieval of holograms.
* Provides methods to register, unregister, and query holograms by their name.
*/
public interface HologramRegistry {
/**
* Registers a hologram in the registry.
*
* @param hologram the hologram to be registered
* @return {@code true} if the registration was successful, otherwise {@code false}
*/
boolean register(Hologram hologram);
/**
* Unregisters the specified hologram from the registry.
*
* @param hologram the hologram to be unregistered
* @return {@code true} if the hologram was successfully unregistered, otherwise {@code false}
*/
boolean unregister(Hologram hologram);
/**
* Checks if a hologram with the specified name exists in the registry.
*
* @param name the name of the hologram to check for existence
* @return {@code true} if a hologram with the specified name exists, otherwise {@code false}
*/
boolean contains(String name);
/**
* Retrieves a hologram by its name from the registry.
*
* @param name the name of the hologram to retrieve
* @return an {@code Optional} containing the hologram if found, or an empty {@code Optional} if no hologram exists with the specified name
*/
Optional<Hologram> get(String name);
/**
* Retrieves a hologram by its name from the registry, ensuring that the hologram exists.
* If no hologram exists with the specified name, this method will throw an exception.
*
* @param name the name of the hologram to retrieve
* @return the hologram associated with the specified name
* @throws IllegalArgumentException if no hologram exists with the given name
*/
Hologram mustGet(String name);
/**
* Retrieves all holograms currently registered in the registry.
*
* @return a collection containing all registered holograms
*/
Collection<Hologram> getAll();
/**
* Retrieves all persistent holograms currently registered in the registry.
*
* @return a collection containing all persistent holograms
*/
Collection<Hologram> getAllPersistent();
/**
* Removes all holograms from the registry, effectively clearing its contents.
*/
void clear();
}

View File

@@ -0,0 +1,75 @@
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.jetbrains.annotations.ApiStatus;
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
@ApiStatus.Internal
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
block = Material.getMaterial(section.getString("block", "GRASS_BLOCK").toUpperCase());
return true;
}
@Override
@ApiStatus.Internal
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;
}
}

View File

@@ -0,0 +1,213 @@
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
@ApiStatus.Internal
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
@ApiStatus.Internal
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;
}
}

View File

@@ -0,0 +1,193 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.FancyHolograms;
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.ApiStatus;
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 = false;
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 FancyHolograms.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
@ApiStatus.Internal
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) {
FancyHolograms.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
@ApiStatus.Internal
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());
}
}

View File

@@ -0,0 +1,76 @@
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 org.jetbrains.annotations.ApiStatus;
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
@ApiStatus.Internal
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
item = section.getItemStack("item", DEFAULT_ITEM);
return true;
}
@Override
@ApiStatus.Internal
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;
}
}

View File

@@ -0,0 +1,229 @@
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 org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public class TextHologramData extends DisplayHologramData {
public static final List<String> DEFAULT_TEXT = List.of("Edit this line with /hologram edit <name>");
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<String> text = new ArrayList<>(DEFAULT_TEXT);
private Color background = null;
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);
}
public List<String> getText() {
return text;
}
public TextHologramData setText(List<String> text) {
if (!Objects.equals(this.text, text)) {
this.text = text;
setHasChanges(true);
}
return this;
}
public TextHologramData addLine(String line) {
text.add(line);
setHasChanges(true);
return this;
}
public TextHologramData removeLine(int index) {
text.remove(index);
setHasChanges(true);
return this;
}
public TextHologramData setLine(int index, String line) {
text.set(index, line);
setHasChanges(true);
return this;
}
public TextHologramData clearText() {
text.clear();
setHasChanges(true);
return this;
}
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
@ApiStatus.Internal
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
@ApiStatus.Internal
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;
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,30 @@
package de.oliver.fancyholograms.api.data.builder;
import de.oliver.fancyholograms.api.data.BlockHologramData;
import org.bukkit.Location;
import org.bukkit.Material;
public class BlockHologramBuilder extends HologramBuilder{
private BlockHologramBuilder(String name, Location location) {
super();
this.data = new BlockHologramData(name, location);
}
/**
* Creates a new instance of BlockHologramBuilder with the specified name and location.
*
* @param name the name of the block hologram
* @param location the location of the block hologram
* @return a new instance of BlockHologramBuilder
*/
public static BlockHologramBuilder create(String name, Location location) {
return new BlockHologramBuilder(name, location);
}
public BlockHologramBuilder block(Material block) {
((BlockHologramData) data).setBlock(block);
return this;
}
}

View File

@@ -0,0 +1,97 @@
package de.oliver.fancyholograms.api.data.builder;
import de.oliver.fancyholograms.api.FancyHolograms;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.data.property.Visibility;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.entity.Display;
import org.joml.Vector3f;
public abstract class HologramBuilder {
protected DisplayHologramData data;
protected HologramBuilder() {
}
/**
* Builds and returns a new Hologram instance using the current configuration
* in the HologramBuilder.
*
* @return a new instance of Hologram created based on the configured data
*/
public Hologram build() {
return FancyHolograms.get().getHologramFactory().apply(data);
}
/**
* Builds a new Hologram instance using the current configuration in the HologramBuilder
* and registers it.
*
* @return a new instance of Hologram that has been registered with the registry
*/
public Hologram buildAndRegister() {
Hologram hologram = build();
FancyHolograms.get().getRegistry().register(hologram);
return hologram;
}
// The following methods are setters for the HologramData class
public HologramBuilder visibilityDistance(int distance) {
data.setVisibilityDistance(distance);
return this;
}
public HologramBuilder visibility(Visibility visibility) {
data.setVisibility(visibility);
return this;
}
public HologramBuilder persistent(boolean persistent) {
data.setPersistent(persistent);
return this;
}
public HologramBuilder linkedNpcName(String linkedNpcName) {
data.setLinkedNpcName(linkedNpcName);
return this;
}
// The following methods are specific to the DisplayHologramData class
public HologramBuilder billboard(Display.Billboard billboard) {
data.setBillboard(billboard);
return this;
}
public HologramBuilder scale(float x, float y, float z) {
data.setScale(new Vector3f(x, y, z));
return this;
}
public HologramBuilder translation(float x, float y, float z) {
data.setTranslation(new Vector3f(x, y, z));
return this;
}
public HologramBuilder brightness(int blockLight, int skyLight) {
data.setBrightness(new Display.Brightness(blockLight, skyLight));
return this;
}
public HologramBuilder shadowRadius(float shadowRadius) {
data.setShadowRadius(shadowRadius);
return this;
}
public HologramBuilder shadowStrength(float shadowStrength) {
data.setShadowStrength(shadowStrength);
return this;
}
public HologramBuilder interpolationDuration(int interpolationDuration) {
data.setInterpolationDuration(interpolationDuration);
return this;
}
}

View File

@@ -0,0 +1,30 @@
package de.oliver.fancyholograms.api.data.builder;
import de.oliver.fancyholograms.api.data.ItemHologramData;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
public class ItemHologramBuilder extends HologramBuilder{
private ItemHologramBuilder(String name, Location location) {
super();
this.data = new ItemHologramData(name, location);
}
/**
* Creates a new instance of ItemHologramBuilder with the specified name and location.
*
* @param name the name of the item hologram
* @param location the location of the item hologram
* @return a new instance of ItemHologramBuilder
*/
public static ItemHologramBuilder create(String name, Location location) {
return new ItemHologramBuilder(name, location);
}
public ItemHologramBuilder item(ItemStack item) {
((ItemHologramData) data).setItemStack(item);
return this;
}
}

View File

@@ -0,0 +1,77 @@
package de.oliver.fancyholograms.api.data.builder;
import de.oliver.fancyholograms.api.data.TextHologramData;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.entity.TextDisplay;
import java.util.List;
public class TextHologramBuilder extends HologramBuilder{
private TextHologramBuilder(String name, Location location) {
super();
this.data = new TextHologramData(name, location);
}
/**
* Creates a new instance of TextHologramBuilder with the specified name and location.
*
* @param name the name of the text hologram
* @param location the location of the text hologram
* @return a new instance of TextHologramBuilder
*/
public static TextHologramBuilder create(String name, Location location) {
return new TextHologramBuilder(name, location);
}
public TextHologramBuilder text(List<String> text) {
((TextHologramData) data).setText(text);
return this;
}
public TextHologramBuilder text(String text) {
return text(List.of(text));
}
public TextHologramBuilder text(String ... text) {
return text(List.of(text));
}
public TextHologramBuilder background(Color background) {
((TextHologramData) data).setBackground(background);
return this;
}
/**
* Sets the background color of the text hologram using a color code in ARGB format.
*
* @param background the ARGB color code as a string (#AARRGGBB)
* @return the updated instance of TextHologramBuilder for method chaining
*/
public TextHologramBuilder background(String background) {
int argb = Integer.parseInt(background.substring(1), 16);
return background(Color.fromARGB(argb));
}
public TextHologramBuilder textAlignment(TextDisplay.TextAlignment textAlignment) {
((TextHologramData) data).setTextAlignment(textAlignment);
return this;
}
public TextHologramBuilder textShadow(boolean textShadow) {
((TextHologramData) data).setTextShadow(textShadow);
return this;
}
public TextHologramBuilder seeThrough(boolean seeThrough) {
((TextHologramData) data).setSeeThrough(seeThrough);
return this;
}
public TextHologramBuilder updateTextInterval(int updateTextInterval) {
((TextHologramData) data).setTextUpdateInterval(updateTextInterval);
return this;
}
}

View File

@@ -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<Visibility> 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
* <br>
* TODO: Discussion needed - Potentially condense this into one singular multimap within the enum?
*/
public static class ManualVisibility {
private static final HashMultimap<String, UUID> distantViewers = HashMultimap.create();
public static boolean canSee(Player player, Hologram hologram) {
return hologram.isViewer(player) || distantViewers.containsEntry(hologram.getData().getName(), player.getUniqueId());
}
public static void addDistantViewer(Hologram hologram, UUID uuid) {
addDistantViewer(hologram.getData().getName(), uuid);
}
public static void addDistantViewer(String hologramName, UUID uuid) {
distantViewers.put(hologramName, uuid);
}
public static void removeDistantViewer(Hologram hologram, UUID uuid) {
removeDistantViewer(hologram.getData().getName(), uuid);
}
public static void removeDistantViewer(String hologramName, UUID uuid) {
distantViewers.remove(hologramName, uuid);
}
public static void remove(Hologram hologram) {
remove(hologram.getData().getName());
}
public static void remove(String hologramName) {
distantViewers.removeAll(hologramName);
}
public static void clear() {
distantViewers.clear();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 HologramDespawnEvent extends HologramEvent {
private static final HandlerList handlerList = new HandlerList();
@NotNull
private final Player player;
public HologramDespawnEvent(@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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 HologramSpawnEvent extends HologramEvent {
private static final HandlerList handlerList = new HandlerList();
@NotNull
private final Player player;
public HologramSpawnEvent(@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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,45 @@
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;
/**
* Represents an event triggered when all holograms are loaded.
* This event contains a list of all holograms that have been loaded in the current context.
* The event is asynchronous if it does not execute on the main server thread.
* <p>
* This event may serve as a notification mechanism to inform listeners that the loading operation
* for holograms has completed.
* <p>
* This event extends the {@link Event} class, utilizing the Bukkit event system.
*/
public final class HologramsLoadEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final ImmutableList<Hologram> holograms;
public HologramsLoadEvent(@NotNull final ImmutableList<Hologram> holograms) {
super(!Bukkit.isPrimaryThread());
this.holograms = holograms;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull ImmutableList<Hologram> getHolograms() {
return this.holograms;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View File

@@ -0,0 +1,41 @@
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;
/**
* Represents an event that is triggered when holograms are unloaded in the system.
* This event contains the list of holograms that are being unloaded.
* <p>
* This event is not cancellable.
*/
public final class HologramsUnloadEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final ImmutableList<Hologram> holograms;
public HologramsUnloadEvent(@NotNull final ImmutableList<Hologram> holograms) {
super(!Bukkit.isPrimaryThread());
this.holograms = holograms;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull ImmutableList<Hologram> getHolograms() {
return this.holograms;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View File

@@ -0,0 +1,192 @@
package de.oliver.fancyholograms.api.hologram;
import com.google.common.collect.Sets;
import de.oliver.fancyholograms.api.FancyHolograms;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.api.data.TextHologramData;
import de.oliver.fancyholograms.api.trait.HologramTrait;
import de.oliver.fancyholograms.api.trait.HologramTraitTrait;
import net.kyori.adventure.text.Component;
import org.bukkit.Color;
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.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
/**
* This class provides core functionalities for managing viewers, spawning, despawning, and updating holograms.
*/
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;
protected final @NotNull Set<UUID> viewers;
protected final @NotNull HologramTraitTrait traitTrait;
protected Hologram(@NotNull final HologramData data) {
this.data = data;
this.viewers = new HashSet<>();
this.traitTrait = new HologramTraitTrait(this);
}
/**
* Forcefully spawns the hologram and makes it visible to the specified player.
*
* @param player the player to whom the hologram should be shown; must not be null
*/
@ApiStatus.Internal
public abstract void spawnTo(@NotNull final Player player);
/**
* Forcefully despawns the hologram and makes it invisible to the specified player.
*
* @param player the player from whom the hologram should be hidden; must not be null
*/
@ApiStatus.Internal
public abstract void despawnFrom(@NotNull final Player player);
/**
* Updates the hologram for the specified player.
*
* @param player the player for whom the hologram should be updated; must not be null
*/
@ApiStatus.Internal
public abstract void updateFor(@NotNull final Player player);
/**
* @return a copy of the set of UUIDs of players currently viewing the hologram
*/
public final @NotNull Set<UUID> getViewers() {
return Sets.newHashSet(this.viewers);
}
@ApiStatus.Internal
public void setViewers(@NotNull final Set<UUID> viewers) {
this.viewers.clear();
this.viewers.addAll(viewers);
}
@ApiStatus.Internal
public void removeViewer(@NotNull final UUID viewer) {
this.viewers.remove(viewer);
}
/**
* @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);
}
@ApiStatus.Experimental
public @NotNull HologramTraitTrait getTraitTrait() {
return traitTrait;
}
@ApiStatus.Experimental
public HologramData addTrait(HologramTrait trait) {
traitTrait.addTrait(trait);
return data;
}
@ApiStatus.Experimental
public HologramData addTrait(Class<? extends HologramTrait> traitClass) {
HologramTrait trait = null;
try {
trait = traitClass.getConstructor(null).newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
FancyHolograms.get().getFancyLogger().error("Failed to instantiate trait " + traitClass.getSimpleName());
FancyHolograms.get().getFancyLogger().error(e);
}
traitTrait.addTrait(trait);
return data;
}
public final @NotNull HologramData getData() {
return this.data;
}
/**
* Retrieves the data associated with the hologram and casts it to the specified type.
*
* @param <T> the type of {@code HologramData} to retrieve
* @param clazz the class of the data type to retrieve; must not be null
* @return the hologram data cast to the specified type
*/
@ApiStatus.Experimental
public final <T extends HologramData> @NotNull T getData(@NotNull Class<T> clazz) {
return clazz.cast(this.data);
}
/**
* Retrieves the data associated with the hologram, if it can be cast to the specified type.
*
* @param <T> the type of {@code HologramData}
* @param clazz the class of the data type to retrieve; must not be null
* @return the hologram data cast to the specified type, or null if the cast fails
*/
@ApiStatus.Experimental
public final <T extends HologramData> @Nullable T getDataNullable(@NotNull Class<T> clazz) {
try {
return clazz.cast(this.data);
} catch (ClassCastException ignored) {
return null;
}
}
/**
* Consumes the data associated with the hologram if it can be cast to the specified type.
*
* @param <T> the type of {@link HologramData} to consume
* @param clazz the class of the data type to consume; must not be null
* @param consumer the action to perform with the consumed data; must not be null
*/
@ApiStatus.Experimental
public final <T extends HologramData> void consumeData(@NotNull Class<T> clazz, @NotNull Consumer<T> consumer) {
final T data = getDataNullable(clazz);
if (data != null) {
consumer.accept(data);
}
}
/**
* 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);
}
}

View File

@@ -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<String> commands;
HologramType(List<String> 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<String> getCommands() {
return commands;
}
}

View File

@@ -0,0 +1,8 @@
package de.oliver.fancyholograms.api.trait;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultTrait {
}

View File

@@ -0,0 +1,106 @@
package de.oliver.fancyholograms.api.trait;
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
import de.oliver.fancyholograms.api.FancyHolograms;
import de.oliver.fancyholograms.api.HologramController;
import de.oliver.fancyholograms.api.HologramRegistry;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import java.util.concurrent.ScheduledExecutorService;
/**
* Represents a trait that can be attached to a hologram. This class provides a structure for
* managing the lifecycle of traits related to holograms. It defines methods to handle
* initialization, attachment, updates, and data persistence.
* <p>
* Subclasses of this abstract class must implement the specific behavior of the trait by
* overriding the provided lifecycle methods.
*/
@ApiStatus.Experimental
public abstract class HologramTrait {
protected final String name;
protected final FancyHolograms api = FancyHolograms.get();
protected final ExtendedFancyLogger logger = api.getFancyLogger();
protected final HologramController controller = api.getController();
protected final HologramRegistry registry = api.getRegistry();
protected final ScheduledExecutorService hologramThread = api.getHologramThread();
protected Hologram hologram;
/**
* Creates a new hologram trait with the given name.
* @param name the name of the trait
*/
public HologramTrait(String name) {
this.name = name;
}
public HologramTrait() {
this.name = getClass().getSimpleName();
}
public void attachHologram(Hologram hologram) {
if (this.hologram != null) {
throw new IllegalStateException("Trait is already attached to a hologram");
}
this.hologram = hologram;
}
/**
* Called when the trait is attached to a hologram.
* The hologram is available at this point.
*/
public void onAttach() {
}
/**
* Called when the hologram is spawned to a player.
*/
public void onSpawn(Player player) {
}
/**
* Called when the hologram is despawned from a player.
*/
public void onDespawn(Player player) {
}
/**
* Called when the hologram is registered in the registry.
*/
public void onRegister() {
}
/**
* Called when the hologram is unregistered from the registry.
*/
public void onUnregister() {
}
/**
* Called when the hologram is being loaded.
* In this method you should load all necessary data for the trait.
*/
public void load() {
}
/**
* Called when the hologram is being saved.
* In this method you should save all necessary data for the trait.
*/
public void save() {
}
public String getName() {
return name;
}
public Hologram getHologram() {
return hologram;
}
}

View File

@@ -0,0 +1,21 @@
package de.oliver.fancyholograms.api.trait;
import org.jetbrains.annotations.ApiStatus;
import java.util.List;
@ApiStatus.Experimental
public interface HologramTraitRegistry {
@ApiStatus.Experimental
boolean register(Class<? extends HologramTrait> trait);
@ApiStatus.Experimental
boolean unregister(Class<? extends HologramTrait> trait);
@ApiStatus.Experimental
boolean isRegistered(Class<? extends HologramTrait> trait);
@ApiStatus.Experimental
List<Class<? extends HologramTrait>> getRegisteredTraits();
}

View File

@@ -0,0 +1,86 @@
package de.oliver.fancyholograms.api.trait;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
public class HologramTraitTrait extends HologramTrait {
private final List<HologramTrait> traits;
public HologramTraitTrait(Hologram hologram) {
super("trait");
attachHologram(hologram);
this.traits = new ArrayList<>();
}
public void addTrait(HologramTrait trait) {
this.traits.add(trait);
trait.attachHologram(hologram);
trait.onAttach();
}
@Override
public void onAttach() {
List<Class<? extends HologramTrait>> registeredTraits = api.getTraitRegistry().getRegisteredTraits();
for (Class<? extends HologramTrait> traitClass : registeredTraits) {
if (!traitClass.isAnnotationPresent(DefaultTrait.class)) {
continue;
}
try {
HologramTrait trait = traitClass.getConstructor().newInstance();
this.traits.add(trait);
logger.debug("Attached default trait " + traitClass.getName() + " to hologram " + hologram.getData().getName());
trait.onAttach();
} catch (Exception e) {
logger.error("Failed to instantiate trait " + traitClass.getName());
logger.error(e);
}
}
}
@Override
public void onSpawn(Player player) {
for (HologramTrait trait : this.traits) {
trait.onSpawn(player);
}
}
@Override
public void onDespawn(Player player) {
for (HologramTrait trait : this.traits) {
trait.onDespawn(player);
}
}
@Override
public void onRegister() {
for (HologramTrait trait : this.traits) {
trait.onRegister();
}
}
@Override
public void onUnregister() {
for (HologramTrait trait : this.traits) {
trait.onUnregister();
}
}
@Override
public void load() {
for (HologramTrait trait : this.traits) {
trait.load();
}
}
@Override
public void save() {
for (HologramTrait trait : this.traits) {
trait.save();
}
}
}

View File

@@ -0,0 +1,264 @@
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") version "2.3.1"
id("com.gradleup.shadow") version "8.3.6"
id("net.minecrell.plugin-yml.paper") version "0.6.0"
id("io.papermc.hangar-publish-plugin") version "0.1.2"
id("com.modrinth.minotaur") version "2.+"
}
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::api"))
implementation(project(":plugins:fancyholograms::implementation_1_20_4", configuration = "reobf"))
implementation(project(":plugins:fancyholograms::implementation_1_20_2", configuration = "reobf"))
implementation(project(":plugins:fancyholograms::implementation_1_20_1", configuration = "reobf"))
implementation(project(":plugins:fancyholograms::implementation_1_19_4", configuration = "reobf"))
implementation("de.oliver:FancyLib:35")
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("com.viaversion:viaversion-api:5.2.0")
compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT")
}
paper {
main = "de.oliver.fancyholograms.main.FancyHologramsPlugin"
bootstrapper = "de.oliver.fancyholograms.main.FancyHologramsBootstrapper"
loader = "de.oliver.fancyholograms.main.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
}
register("ViaVersion") {
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("")
dependsOn(":api:shadowJar")
}
publishing {
repositories {
maven {
name = "fancypluginsReleases"
url = uri("https://repo.fancyplugins.de/releases")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
maven {
name = "fancypluginsSnapshots"
url = uri("https://repo.fancyplugins.de/snapshots")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(project.components["java"])
}
}
}
compileJava {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
options.release = 21
// For cloud-annotations, see https://cloud.incendo.org/annotations/#command-components
options.compilerArgs.add("-parameters")
}
javadoc {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
}
processResources {
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
val props = mapOf(
"description" to project.description,
"version" to project.version,
"hash" to getCurrentCommitHash(),
"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))
}
fun getCurrentCommitHash(): String {
val process = ProcessBuilder("git", "rev-parse", "HEAD").start()
val reader = BufferedReader(InputStreamReader(process.inputStream))
val commitHash = reader.readLine()
reader.close()
process.waitFor()
if (process.exitValue() == 0) {
return commitHash ?: ""
} else {
throw IllegalStateException("Failed to retrieve the commit hash.")
}
}
fun getLastCommitMessage(): String {
val process = ProcessBuilder("git", "log", "-1", "--pretty=%B").start()
val reader = BufferedReader(InputStreamReader(process.inputStream))
val commitMessage = reader.readLine()
reader.close()
process.waitFor()
if (process.exitValue() == 0) {
return commitMessage ?: ""
} else {
throw IllegalStateException("Failed to retrieve the commit message.")
}
}
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 = getLastCommitMessage()
}
}
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(getLastCommitMessage())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

View File

@@ -0,0 +1,33 @@
plugins {
id("java-library")
id("io.papermc.paperweight.userdev") version "1.7.7"
}
val minecraftVersion = "1.19.4"
dependencies {
paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT")
implementation(project(":plugins:fancyholograms:api"))
implementation("de.oliver:FancyLib:35")
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)
}
}

View File

@@ -0,0 +1,253 @@
package de.oliver.fancyholograms.hologram.version;
import com.mojang.math.Transformation;
import de.oliver.fancyholograms.api.data.*;
import de.oliver.fancyholograms.api.events.HologramDespawnEvent;
import de.oliver.fancyholograms.api.events.HologramSpawnEvent;
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);
create();
}
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);
}
}
public void syncWithData() {
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<Integer>) 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<Integer>) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND);
} else if (background == Hologram.TRANSPARENT) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, 0);
} else {
display.getEntityData().set((EntityDataAccessor<Integer>) 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) {
// interpolation
final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_19_4.DATA_INTERPOLATION_DURATION_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_INTERPOLATION_DURATION_ID, displayData.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<Integer>) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0);
// 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 void spawnTo(@NotNull final Player player) {
if (!new HologramSpawnEvent(this, player).callEvent()) {
return;
}
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; // could not be created, nothing to show
}
if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) {
return;
}
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());
updateFor(player);
}
@Override
public void despawnFrom(@NotNull final Player player) {
if (!new HologramDespawnEvent(this, player).callEvent()) {
return;
}
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to hide
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId()));
this.viewers.remove(player.getUniqueId());
}
@Override
public void updateFor(@NotNull final Player player) {
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to refresh
}
if (!isViewer(player)) {
return;
}
syncWithData();
((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display));
if (display instanceof TextDisplay textDisplay) {
textDisplay.setText(PaperAdventure.asVanilla(getShownText(player)));
}
final var values = new ArrayList<DataValue<?>>();
//noinspection unchecked
for (final var item : ((Int2ObjectMap<DataItem<?>>) getValue(display.getEntityData(), "e")).values()) {
values.add(item.value());
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,33 @@
plugins {
id("java-library")
id("io.papermc.paperweight.userdev") version "1.7.7"
}
val minecraftVersion = "1.20.1"
dependencies {
paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT")
implementation(project(":plugins:fancyholograms:api"))
implementation("de.oliver:FancyLib:35")
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)
}
}

View File

@@ -0,0 +1,253 @@
package de.oliver.fancyholograms.hologram.version;
import com.mojang.math.Transformation;
import de.oliver.fancyholograms.api.data.*;
import de.oliver.fancyholograms.api.events.HologramDespawnEvent;
import de.oliver.fancyholograms.api.events.HologramSpawnEvent;
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);
create();
}
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);
}
}
public void syncWithData() {
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_20_1.DATA_LINE_WIDTH_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) 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<Integer>) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND);
} else if (background == Hologram.TRANSPARENT) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, 0);
} else {
display.getEntityData().set((EntityDataAccessor<Integer>) 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) {
// interpolation
final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_1.DATA_INTERPOLATION_DURATION_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_INTERPOLATION_DURATION_ID, displayData.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<Integer>) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0);
// 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 void spawnTo(@NotNull final Player player) {
if (!new HologramSpawnEvent(this, player).callEvent()) {
return;
}
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; // could not be created, nothing to show
}
if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) {
return;
}
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());
updateFor(player);
}
@Override
public void despawnFrom(@NotNull final Player player) {
if (!new HologramDespawnEvent(this, player).callEvent()) {
return;
}
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to hide
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId()));
this.viewers.remove(player.getUniqueId());
}
@Override
public void updateFor(@NotNull final Player player) {
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to refresh
}
if (!isViewer(player)) {
return;
}
syncWithData();
((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display));
if (display instanceof TextDisplay textDisplay) {
textDisplay.setText(PaperAdventure.asVanilla(getShownText(player)));
}
final var values = new ArrayList<DataValue<?>>();
//noinspection unchecked
for (final var item : ((Int2ObjectMap<DataItem<?>>) getValue(display.getEntityData(), "e")).values()) {
values.add(item.value());
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,33 @@
plugins {
id("java-library")
id("io.papermc.paperweight.userdev") version "1.7.7"
}
val minecraftVersion = "1.20.2"
dependencies {
paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT")
implementation(project(":plugins:fancyholograms:api"))
implementation("de.oliver:FancyLib:35")
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)
}
}

View File

@@ -0,0 +1,253 @@
package de.oliver.fancyholograms.hologram.version;
import com.mojang.math.Transformation;
import de.oliver.fancyholograms.api.data.*;
import de.oliver.fancyholograms.api.events.HologramDespawnEvent;
import de.oliver.fancyholograms.api.events.HologramSpawnEvent;
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);
create();
}
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);
}
}
public void syncWithData() {
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_20_2.DATA_LINE_WIDTH_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) 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()); //DATA_BACKGROUND_COLOR_ID
final var background = textData.getBackground();
if (background == null) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND);
} else if (background == Hologram.TRANSPARENT) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, 0);
} else {
display.getEntityData().set((EntityDataAccessor<Integer>) 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) {
// interpolation
final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_2.DATA_INTERPOLATION_DURATION_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_INTERPOLATION_DURATION_ID, displayData.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<Integer>) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0);
// 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 void spawnTo(@NotNull final Player player) {
if (!new HologramSpawnEvent(this, player).callEvent()) {
return;
}
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; // could not be created, nothing to show
}
if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) {
return;
}
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());
updateFor(player);
}
@Override
public void despawnFrom(@NotNull final Player player) {
if (!new HologramDespawnEvent(this, player).callEvent()) {
return;
}
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to hide
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId()));
this.viewers.remove(player.getUniqueId());
}
@Override
public void updateFor(@NotNull final Player player) {
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to refresh
}
if (!isViewer(player)) {
return;
}
syncWithData();
((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display));
if (display instanceof TextDisplay textDisplay) {
textDisplay.setText(PaperAdventure.asVanilla(getShownText(player)));
}
final var values = new ArrayList<DataValue<?>>();
//noinspection unchecked
for (final var item : ((Int2ObjectMap<DataItem<?>>) getValue(display.getEntityData(), "e")).values()) {
values.add(item.value());
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,33 @@
plugins {
id("java-library")
id("io.papermc.paperweight.userdev") version "1.7.7"
}
val minecraftVersion = "1.20.4"
dependencies {
paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT")
implementation(project(":plugins:fancyholograms:api"))
implementation("de.oliver:FancyLib:35")
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)
}
}

View File

@@ -0,0 +1,253 @@
package de.oliver.fancyholograms.hologram.version;
import com.mojang.math.Transformation;
import de.oliver.fancyholograms.api.data.*;
import de.oliver.fancyholograms.api.events.HologramDespawnEvent;
import de.oliver.fancyholograms.api.events.HologramSpawnEvent;
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);
create();
}
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);
}
}
public void syncWithData() {
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_20_4.TEXT_DISPLAY__DATA_LINE_WIDTH_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) 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()); //DATA_BACKGROUND_COLOR_ID
final var background = textData.getBackground();
if (background == null) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, TextDisplay.INITIAL_BACKGROUND);
} else if (background == Hologram.TRANSPARENT) {
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_BACKGROUND_COLOR_ID, 0);
} else {
display.getEntityData().set((EntityDataAccessor<Integer>) 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) {
// interpolation
final var DATA_INTERPOLATION_DURATION_ID = ReflectionUtils.getStaticValue(Display.class, MappingKeys1_20_4.DISPLAY__DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID.getMapping());
display.getEntityData().set((EntityDataAccessor<Integer>) DATA_INTERPOLATION_DURATION_ID, displayData.getInterpolationDuration());
final var DATA_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<Integer>) DATA_INTERPOLATION_START_DELTA_TICKS_ID, 0);
// 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 void spawnTo(@NotNull final Player player) {
if (!new HologramSpawnEvent(this, player).callEvent()) {
return;
}
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; // could not be created, nothing to show
}
if (!data.getLocation().getWorld().getName().equals(player.getLocation().getWorld().getName())) {
return;
}
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());
updateFor(player);
}
@Override
public void despawnFrom(@NotNull final Player player) {
if (!new HologramDespawnEvent(this, player).callEvent()) {
return;
}
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to hide
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundRemoveEntitiesPacket(display.getId()));
this.viewers.remove(player.getUniqueId());
}
@Override
public void updateFor(@NotNull final Player player) {
final var display = this.display;
if (display == null) {
return; // doesn't exist, nothing to refresh
}
if (!isViewer(player)) {
return;
}
syncWithData();
((CraftPlayer) player).getHandle().connection.send(new ClientboundTeleportEntityPacket(display));
if (display instanceof TextDisplay textDisplay) {
textDisplay.setText(PaperAdventure.asVanilla(getShownText(player)));
}
final var values = new ArrayList<DataValue<?>>();
//noinspection unchecked
for (final var item : ((Int2ObjectMap<DataItem<?>>) getValue(display.getEntityData(), "e")).values()) {
values.add(item.value());
}
((CraftPlayer) player).getHandle().connection.send(new ClientboundSetEntityDataPacket(display.getId(), values));
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,146 @@
package de.oliver.fancyholograms.commands;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.converter.ConverterTarget;
import de.oliver.fancyholograms.converter.FHConversionRegistry;
import de.oliver.fancyholograms.converter.HologramConversionSession;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancylib.MessageHelper;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public final class FancyHologramsCMD extends Command {
public static final String FH_COMMAND_USAGE = "/fancyholograms <save|reload|version|convert>";
@NotNull
private final FancyHologramsPlugin plugin;
public FancyHologramsCMD(@NotNull final FancyHologramsPlugin 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, FH_COMMAND_USAGE);
return false;
}
switch (args[0].toLowerCase(Locale.ROOT)) {
case "save" -> {
this.plugin.savePersistentHolograms();
MessageHelper.success(sender, "Saved all holograms");
}
case "reload" -> {
this.plugin.getHologramConfiguration().reload(plugin);
this.plugin.getRegistry().clear();
for (World world : Bukkit.getWorlds()) {
Collection<HologramData> hologramData = this.plugin.getStorage().loadAll(world.getName());
for (HologramData data : hologramData) {
Hologram hologram = this.plugin.getHologramFactory().apply(data);
this.plugin.getRegistry().register(hologram);
}
}
MessageHelper.success(sender, "Reloaded config and holograms");
}
case "version" -> {
FancyHologramsPlugin.get().getHologramThread().submit(() -> {
FancyHologramsPlugin.get().getVersionConfig().checkVersionAndDisplay(sender, false);
});
}
case "convert" -> {
if (args.length < 3) {
MessageHelper.info(sender, "Usage: /fancyholograms convert <type> <targets> [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<HologramData> holograms = converter.convert(session);
for (final HologramData data : holograms) {
final Hologram hologram = this.plugin.getHologramFactory().apply(data);
this.plugin.getRegistry().register(hologram);
}
this.plugin.savePersistentHolograms();
// 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, FH_COMMAND_USAGE);
return false;
}
}
return true;
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) throws IllegalArgumentException {
if (args.length < 1) {
return Collections.emptyList();
}
List<String> 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();
}
}

View File

@@ -0,0 +1,114 @@
package de.oliver.fancyholograms.commands;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancyholograms.tests.FHTests;
import de.oliver.fancylib.MessageHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
public class FancyHologramsTestCMD extends Command {
@NotNull
private final FancyHologramsPlugin plugin;
public FancyHologramsTestCMD(@NotNull final FancyHologramsPlugin plugin) {
super("FancyHologramsTest");
setPermission("fancyholograms.admin");
this.plugin = plugin;
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
return Collections.emptyList();
}
@Override
public boolean execute(@NotNull CommandSender commandSender, @NotNull String s, @NotNull String[] strings) {
Player p = (Player) commandSender;
FHTests tests = new FHTests();
if(tests.runAllTests(p)) {
MessageHelper.success(p, "All tests have been successfully run!");
} else {
MessageHelper.error(p, "There was an issue running the tests!");
}
return true;
}
// @Override
// public @NotNull List<String> 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(
// "<rainbow><b>This is a test hologram! (#" + n + ")</b></rainbow>",
// "<red>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<green>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<yellow>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<gradient:red:green>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<gradient:green:yellow>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.getHologramFactory().apply(textData);
// hologram.spawnTo(p);
// }
// }
//
// return true;
// } else if (args.length == 1 && args[0].equalsIgnoreCase("test1")) {
// TextHologramData textData = new TextHologramData("holo-test1", p.getLocation());
// textData.setText(Arrays.asList(
// "<rainbow><b>This is a test hologram!</b></rainbow>",
// "<red>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<green>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<yellow>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<gradient:red:green>Lorem ipsum dolor sit amet, consec tetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris.",
// "<gradient:green:yellow>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.getHologramFactory().apply(textData);
// hologram.spawnTo(p);
// }
//
// return false;
// }
}

View File

@@ -0,0 +1,388 @@
package de.oliver.fancyholograms.commands;
import com.google.common.primitives.Ints;
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.main.FancyHologramsPlugin;
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 {
private static final String HELP_TEXT = """
<%primary_color%><b>FancyHolograms commands help:<reset>
<%primary_color%>- /hologram help <dark_gray>- <white>Shows all (sub)commands
<%primary_color%>- /hologram list <dark_gray>- <white>Shows you a overview of all holograms
<%primary_color%>- /hologram nearby <range> <dark_gray>- <white>Shows all holograms nearby you in a range
<%primary_color%>- /hologram teleport <name> <dark_gray>- <white>Teleports you to a hologram
<%primary_color%>- /hologram create <name> <dark_gray>- <white>Creates a new hologram
<%primary_color%>- /hologram remove <name> <dark_gray>- <white>Removes a hologram
<%primary_color%>- /hologram copy <hologram> <new name> <dark_gray>- <white>Copies a hologram
<%primary_color%>- /hologram edit <hologram> addLine <text ...> <dark_gray>- <white>Adds a line at the bottom
<%primary_color%>- /hologram edit <hologram> removeLine <dark_gray>- <white>Removes a line at the bottom
<%primary_color%>- /hologram edit <hologram> insertBefore <line number> <text ...> <dark_gray>- <white>Inserts a line before another
<%primary_color%>- /hologram edit <hologram> insertAfter <line number> <text ...> <dark_gray>- <white>Inserts a line after another
<%primary_color%>- /hologram edit <hologram> setLine <line number> <text ...> <dark_gray>- <white>Edits the line
<%primary_color%>- /hologram edit <hologram> position <dark_gray>- <white>Teleports the hologram to you
<%primary_color%>- /hologram edit <hologram> moveTo <x> <y> <z> [yaw] [pitch] <dark_gray>- <white>Teleports the hologram to the coordinates
<%primary_color%>- /hologram edit <hologram> rotate <degrees> <dark_gray>- <white>Rotates the hologram
<%primary_color%>- /hologram edit <hologram> scale <factor> <dark_gray>- <white>Changes the scale of the hologram
<%primary_color%>- /hologram edit <hologram> billboard <center|fixed|horizontal|vertical> <factor> <dark_gray>- <white>Changes the billboard of the hologram
<%primary_color%>- /hologram edit <hologram> background <color> <dark_gray>- <white>Changes the background of the hologram
<%primary_color%>- /hologram edit <hologram> textShadow <true|false> <dark_gray>- <white>Enables/disables the text shadow
<%primary_color%>- /hologram edit <hologram> textAlignment <alignment> <dark_gray>- <white>Sets the text alignment
<%primary_color%>- /hologram edit <hologram> seeThrough <true|false> <dark_gray>- <white>Enables/disables whether the text can be seen through blocks
<%primary_color%>- /hologram edit <hologram> shadowRadius <value> <dark_gray>- <white>Changes the shadow radius of the hologram
<%primary_color%>- /hologram edit <hologram> shadowStrength <value> <dark_gray>- <white>Changes the shadow strength of the hologram
<%primary_color%>- /hologram edit <hologram> brightness <block|sky> <0-15> <dark_gray>- <white>Changes the brightness of the hologram
<%primary_color%>- /hologram edit <hologram> updateTextInterval <seconds> <dark_gray>- <white>Sets the interval for updating the text
""".replace("%primary_color%", MessageHelper.getPrimaryColor());
private static final String HELP_TEXT_NPCS = """
<%primary_color%>- /hologram edit <hologram> linkWithNpc <npc name> <dark_gray>- <white>Links the hologram with an NPC
<%primary_color%>- /hologram edit <hologram> unlinkWithNpc <dark_gray>- <white>Unlinks the hologram with an NPC
""".replace("%primary_color%", MessageHelper.getPrimaryColor());
@NotNull
private final FancyHologramsPlugin plugin;
public HologramCMD(@NotNull final FancyHologramsPlugin 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, HELP_TEXT + (!PluginUtils.isFancyNpcsEnabled() ? "" : "\n" + 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.getRegistry().get(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) {
plugin.getController().refreshHologram(hologram, p);
}
//TODO: idk
// hologram.queueUpdate();
}
yield updated;
}
default -> false;
};
}
@Override
public @NotNull List<String> 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.getRegistry().getAllPersistent()
.stream()
.map(hologram -> hologram.getData().getName())
.filter(input -> input.toLowerCase().startsWith(args[1].toLowerCase(Locale.ROOT)))
.toList();
}
final var hologram = this.plugin.getRegistry().get(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<String> 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.<String>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<String>();
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;
};
}
}

View File

@@ -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<String> tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args);
boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args);
}

View File

@@ -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<String> 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);
}
}

View File

@@ -0,0 +1,91 @@
package de.oliver.fancyholograms.commands.hologram;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed background color");
return true;
}
}

View File

@@ -0,0 +1,73 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.base.Enums;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed the billboard to " + StringUtils.capitalize(billboard.name().toLowerCase(Locale.ROOT)));
return true;
}
}

View File

@@ -0,0 +1,68 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.data.BlockHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Set block to '" + block.name() + "'");
return true;
}
}

View File

@@ -0,0 +1,71 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed " + brightnessType.toLowerCase() + " brightness to " + brightnessValue);
return true;
}
}

View File

@@ -0,0 +1,52 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancyholograms.util.Formats;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Centered the hologram to %s/%s/%s %s\u00B0 %s\u00B0".formatted(
Formats.COORDINATES_DECIMAL.format(location.x()),
Formats.COORDINATES_DECIMAL.format(location.y()),
Formats.COORDINATES_DECIMAL.format(location.z()),
Formats.COORDINATES_DECIMAL.format((location.getYaw() + 180f) % 360f),
Formats.COORDINATES_DECIMAL.format((location.getPitch()) % 360f)
));
return true;
}
}

View File

@@ -0,0 +1,80 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.events.HologramCreateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getRegistry().get(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 = FancyHologramsPlugin.get().getHologramFactory().apply(data);
if (!new HologramCreateEvent(copy, player).callEvent()) {
MessageHelper.error(sender, "Creating the copied hologram was cancelled");
return false;
}
FancyHologramsPlugin.get().getController().refreshHologram(copy, Bukkit.getOnlinePlayers());
FancyHologramsPlugin.get().getRegistry().register(copy);
if (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(sender, "Copied the hologram");
return true;
}
}

View File

@@ -0,0 +1,90 @@
package de.oliver.fancyholograms.commands.hologram;
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.events.HologramCreateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.api.hologram.HologramType;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getRegistry().get(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 = FancyHologramsPlugin.get().getHologramFactory().apply(displayData);
if (!new HologramCreateEvent(holo, player).callEvent()) {
MessageHelper.error(player, "Creating the hologram was cancelled");
return false;
}
FancyHologramsPlugin.get().getController().refreshHologram(holo, Bukkit.getOnlinePlayers());
FancyHologramsPlugin.get().getRegistry().register(holo);
MessageHelper.success(player, "Created the hologram");
return true;
}
}

View File

@@ -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<String> 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, "<b>Information about the " + hologram.getData().getName() + " hologram:");
MessageHelper.info(player, "Name: <gray>" + hologram.getData().getName());
MessageHelper.info(player, "Type: <gray>" + hologram.getData().getType().name());
MessageHelper.info(player, "Location: <gray>" + data.getLocation().getWorld().getName() + " " + data.getLocation().getX() + " / " + data.getLocation().getY() + " / " + data.getLocation().getZ());
MessageHelper.info(player, "Visibility distance: <gray>" + 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: <gray>x" + displayData.getScale().x());
} else {
MessageHelper.info(player, "Scale: <gray>" + displayData.getScale().x() + ", " + displayData.getScale().y() + ", " + displayData.getScale().z());
}
MessageHelper.info(player, "Billboard: <gray>" + displayData.getBillboard().name());
MessageHelper.info(player, "Shadow radius: <gray>" + displayData.getShadowRadius());
MessageHelper.info(player, "Shadow strength: <gray>" + displayData.getShadowStrength());
}
if (data.getLinkedNpcName() != null) {
MessageHelper.info(player, "Linked npc: <gray>" + data.getLinkedNpcName());
}
if (data instanceof TextHologramData textData) {
MessageHelper.info(player, "Text: ");
for (String line : textData.getText()) {
MessageHelper.info(player, " <reset> " + line);
}
if (textData.getBackground() != null) {
MessageHelper.info(player, "Background: <gray>" + '#' + Integer.toHexString(textData.getBackground().asARGB()));
} else {
MessageHelper.info(player, "Background: <gray>default");
}
MessageHelper.info(player, "Text alignment: <gray>" + textData.getTextAlignment().name());
MessageHelper.info(player, "See through: <gray>" + (textData.isSeeThrough() ? "enabled" : "disabled"));
MessageHelper.info(player, "Text shadow: <gray>" + (textData.hasTextShadow() ? "enabled" : "disabled"));
if (textData.getTextUpdateInterval() == -1) {
MessageHelper.info(player, "Update text interval: <gray>not updating");
} else {
MessageHelper.info(player, "Update text interval: <gray>" + textData.getTextUpdateInterval() + " ticks");
}
} else if (data instanceof BlockHologramData blockData) {
MessageHelper.info(player, "Block: <gray>" + blockData.getBlock().name());
} else if (data instanceof ItemHologramData itemData) {
MessageHelper.info(player, "Item: <gray>" + itemData.getItemStack().getType().name());
}
return true;
}
}

View File

@@ -0,0 +1,80 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Inserted line");
return true;
}
}

View File

@@ -0,0 +1,82 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Inserted line");
return true;
}
}

View File

@@ -0,0 +1,76 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.data.ItemHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Set the item to '" + item.getType().name() + "'");
return true;
}
}

View File

@@ -0,0 +1,60 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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:open_url:'https://modrinth.com/plugin/fancynpcs/versions'><u>click here</u></click>.");
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());
FancyHologramsPlugin.get().getControllerImpl().syncHologramWithNpc(hologram);
if (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Linked hologram with NPC");
return true;
}
}

View File

@@ -0,0 +1,78 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancyholograms.util.Formats;
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<String> 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 = FancyHologramsPlugin.get().getRegistry().getAllPersistent();
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, "<b>List of holograms:</b>");
MessageHelper.info(player, "<b>Page %s/%s</b>".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,
"<hover:show_text:'<gray><i>Click to teleport</i></gray>'><click:run_command:'%s'> - %s (%s/%s/%s in %s)</click></hover>"
.formatted("/hologram teleport " + holo.getData().getName(),
holo.getData().getName(),
Formats.DECIMAL.format(location.x()),
Formats.DECIMAL.format(location.y()),
Formats.DECIMAL.format(location.z()),
location.getWorld().getName()
));
});
}
return true;
}
}

View File

@@ -0,0 +1,94 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Doubles;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancyholograms.util.Formats;
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.getData().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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Moved the hologram to %s/%s/%s %s\u00B0 %s\u00B0".formatted(
Formats.COORDINATES_DECIMAL.format(newLocation.x()),
Formats.COORDINATES_DECIMAL.format(newLocation.y()),
Formats.COORDINATES_DECIMAL.format(newLocation.z()),
Formats.COORDINATES_DECIMAL.format((newLocation.getYaw() + 180f) % 360f),
Formats.COORDINATES_DECIMAL.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<Location, Number> 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<String> 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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -0,0 +1,96 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancyholograms.util.Formats;
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 {
public static final String INVALID_NEARBY_RANGE = "Provide an integer radius to search for holograms nearby.";
@Override
public List<String> 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, INVALID_NEARBY_RANGE);
return false;
}
Optional<Integer> range = NumberHelper.parseInt(args[1]);
if (range.isEmpty()) {
MessageHelper.error(player, INVALID_NEARBY_RANGE);
return false;
}
Location playerLocation = ((Player) player).getLocation().clone();
List<Map.Entry<Hologram, Double>> nearby = FancyHologramsPlugin.get()
.getRegistry()
.getAllPersistent()
.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, "<b>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,
"<hover:show_text:'<gray><i>Click to teleport</i></gray>'><click:run_command:'%s'> - %s (%s/%s/%s in %s, %s blocks away)</click></hover>"
.formatted(
"/hologram teleport " + holo.getData().getName(),
holo.getData().getName(),
Formats.DECIMAL.format(location.x()),
Formats.DECIMAL.format(location.y()),
Formats.DECIMAL.format(location.z()),
location.getWorld().getName(),
Formats.DECIMAL.format(distance)
));
});
return true;
}
}

View File

@@ -0,0 +1,42 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.FancyHolograms;
import de.oliver.fancyholograms.api.events.HologramDeleteEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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;
}
FancyHolograms.get().getHologramThread().submit(() -> {
FancyHologramsPlugin.get().getRegistry().unregister(hologram);
MessageHelper.success(player, "Removed the hologram");
});
return true;
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -0,0 +1,80 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Floats;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed scale to " + scaleX + ", " + scaleY + ", " + scaleZ);
return true;
}
}

View File

@@ -0,0 +1,74 @@
package de.oliver.fancyholograms.commands.hologram;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed see through");
return true;
}
}

View File

@@ -0,0 +1,87 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed text for line " + (Math.min(index, lines.size() - 1) + 1));
return true;
}
@Override
public List<String> 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);
}
}

View File

@@ -0,0 +1,70 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Floats;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed shadow radius");
return true;
}
}

View File

@@ -0,0 +1,70 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Floats;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed shadow strength");
return true;
}
}

View File

@@ -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<String> 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;
}
}

View File

@@ -0,0 +1,72 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.base.Enums;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed text alignment");
return true;
}
}

View File

@@ -0,0 +1,74 @@
package de.oliver.fancyholograms.commands.hologram;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed text shadow");
return true;
}
}

View File

@@ -0,0 +1,80 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Floats;
import de.oliver.fancyholograms.api.data.DisplayHologramData;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed translation to " + translateX + ", " + translateY + ", " + translateZ);
return true;
}
}

View File

@@ -0,0 +1,57 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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:open_url:'https://modrinth.com/plugin/fancynpcs/versions'><u>click here</u></click>.");
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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Unlinked hologram with NPC");
return true;
}
}

View File

@@ -0,0 +1,97 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
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.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed the text update interval");
return true;
}
}

View File

@@ -0,0 +1,56 @@
package de.oliver.fancyholograms.commands.hologram;
import de.oliver.fancyholograms.api.data.property.Visibility;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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.getData().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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed visibility to " + visibility);
return true;
}
}

View File

@@ -0,0 +1,68 @@
package de.oliver.fancyholograms.commands.hologram;
import com.google.common.primitives.Ints;
import de.oliver.fancyholograms.api.events.HologramUpdateEvent;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.commands.HologramCMD;
import de.oliver.fancyholograms.commands.Subcommand;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<String> 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.getData().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 (FancyHologramsPlugin.get().getHologramConfiguration().isSaveOnChangedEnabled()) {
FancyHologramsPlugin.get().getStorage().save(hologram.getData());
}
MessageHelper.success(player, "Changed visibility distance");
return true;
}
}

View File

@@ -0,0 +1,126 @@
package de.oliver.fancyholograms.config;
import de.oliver.fancyholograms.api.FancyHolograms;
import de.oliver.fancyholograms.api.HologramConfiguration;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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 FHConfiguration 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.
* <p>
* 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;
@Override
public void reload(@NotNull FancyHolograms plugin) {
FancyHologramsPlugin pluginImpl = (FancyHologramsPlugin) 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");
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;
}
public String getLogLevel() {
return logLevel;
}
}

View File

@@ -0,0 +1,19 @@
package de.oliver.fancyholograms.config;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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 final FeatureFlag DISABLE_HOLOGRAMS_FOR_OLD_CLIENTS = new FeatureFlag("disable-holograms-for-old-clients", "Do not show holograms to clients with a version older than 1.19.4", false);
public static void load() {
FeatureFlagConfig config = new FeatureFlagConfig(FancyHologramsPlugin.get());
config.addFeatureFlag(DISABLE_HOLOGRAMS_FOR_BEDROCK_PLAYERS);
config.addFeatureFlag(DISABLE_HOLOGRAMS_FOR_OLD_CLIENTS);
config.load();
}
}

View File

@@ -0,0 +1,168 @@
package de.oliver.fancyholograms.controller;
import com.google.common.cache.CacheBuilder;
import de.oliver.fancyholograms.api.HologramController;
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.hologram.Hologram;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
import de.oliver.fancynpcs.api.FancyNpcsPlugin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class HologramControllerImpl implements HologramController {
@Override
public void showHologramTo(@NotNull final Hologram hologram, @NotNull final Player... players) {
for (Player player : players) {
boolean isVisible = hologram.isViewer(player);
boolean shouldSee = shouldSeeHologram(hologram, player);
if (isVisible || !shouldSee) {
continue;
}
hologram.spawnTo(player);
}
}
@Override
public void hideHologramFrom(@NotNull final Hologram hologram, @NotNull final Player... players) {
for (Player player : players) {
boolean isVisible = hologram.isViewer(player);
boolean shouldSee = shouldSeeHologram(hologram, player);
if (!isVisible || shouldSee) {
continue;
}
hologram.despawnFrom(player);
}
}
@Override
public boolean shouldSeeHologram(@NotNull final Hologram hologram, @NotNull final Player player) {
if (!meetsVisibilityConditions(hologram, player)) {
return false;
}
return isWithinVisibilityDistance(hologram, player);
}
@Override
public void refreshHologram(@NotNull final Hologram hologram, @NotNull final Player... players) {
hideHologramFrom(hologram, players);
showHologramTo(hologram, players);
}
private boolean meetsVisibilityConditions(@NotNull final Hologram hologram, @NotNull final Player player) {
return hologram.getData().getVisibility().canSee(player, hologram);
}
private boolean isWithinVisibilityDistance(@NotNull final Hologram hologram, @NotNull final Player player) {
final var location = hologram.getData().getLocation();
if (!location.getWorld().equals(player.getWorld())) {
return false;
}
int visibilityDistance = hologram.getData().getVisibilityDistance();
double distanceSquared = location.distanceSquared(player.getLocation());
return distanceSquared <= visibilityDistance * visibilityDistance;
}
public void initRefreshTask() {
FancyHologramsPlugin.get().getHologramThread().scheduleWithFixedDelay(() -> {
for (Hologram hologram : FancyHologramsPlugin.get().getRegistry().getAll()) {
refreshHologram(hologram, Bukkit.getOnlinePlayers().toArray(new Player[0]));
}
}, 0, 1, TimeUnit.SECONDS);
}
public void initUpdateTask() {
final var updateTimes = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(5))
.<String, Long>build();
FancyHologramsPlugin.get().getHologramThread().scheduleWithFixedDelay(() -> {
final var time = System.currentTimeMillis();
for (final var hologram : FancyHologramsPlugin.get().getRegistry().getAll()) {
HologramData data = hologram.getData();
if (data.hasChanges()) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
hologram.updateFor(onlinePlayer);
}
data.setHasChanges(false);
if (data instanceof TextHologramData) {
updateTimes.put(hologram.getData().getName(), time);
}
}
}
}, 50, 1000, TimeUnit.MILLISECONDS);
FancyHologramsPlugin.get().getHologramThread().scheduleWithFixedDelay(() -> {
final var time = System.currentTimeMillis();
for (final var hologram : FancyHologramsPlugin.get().getRegistry().getAll()) {
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)) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
hologram.updateFor(onlinePlayer);
}
updateTimes.put(textData.getName(), time);
}
}
}
}, 50, 50, TimeUnit.MILLISECONDS);
}
/**
* 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("<empty>");
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);
}
}

View File

@@ -0,0 +1,57 @@
package de.oliver.fancyholograms.converter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Pattern;
public class ConverterTarget {
private static final ConverterTarget ALL = new ConverterTarget(Pattern.compile(".*"));
private final @NotNull Pattern hologramIdRegex;
public ConverterTarget(@NotNull Pattern matching) {
this.hologramIdRegex = matching;
}
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;
}
}
public @NotNull Pattern getRegex() {
return hologramIdRegex;
}
public boolean matches(@NotNull String hologramId) {
return hologramIdRegex.asMatchPredicate().test(hologramId);
}
}

View File

@@ -0,0 +1,256 @@
package de.oliver.fancyholograms.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<HologramData> 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<String> targetHolograms = getConvertableHolograms()
.stream()
.filter((id) -> spec.getTarget().matches(id))
.toList();
if (targetHolograms.isEmpty()) {
throw new RuntimeException("The provided target matches no holograms.");
}
ArrayList<HologramData> converted = new ArrayList<>();
for (final String id : targetHolograms) {
final List<HologramData> 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<String> 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<HologramData> 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<Map<String, ?>> firstPageSections;
try {
firstPageSections = (List<Map<String, ?>>) firstPage;
} catch (ClassCastException ignored) {
throw new RuntimeException(String.format("The first page for %s is invalid!", hologramId));
}
List<String> 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<HologramData> 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.
* <p>
* 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<HologramData> convertSplitLines(@NotNull TextHologramData base, @NotNull List<Map<String, ?>> lines) {
final List<HologramData> stack = new ArrayList<>();
final List<String> 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<String, ?> 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);
}
}

View File

@@ -0,0 +1,63 @@
package de.oliver.fancyholograms.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<String, HologramConverter> 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<HologramConverter> getConverterById(@NotNull String id) {
return Optional.ofNullable(converters.get(id));
}
public static <T extends HologramConverter> @NotNull Optional<T> getConverter(@NotNull String id) {
return getConverterById(id)
.map((converter) -> {
try {
return (T) converter;
} catch (ClassCastException ignored) {
return null;
}
});
}
public static <T extends HologramConverter> @NotNull Optional<T> getConverter(@NotNull Class<T> clazz) {
return converters.values()
.stream()
.filter(clazz::isInstance)
.findFirst()
.map((converter) -> {
try {
return (T) converter;
} catch (ClassCastException ignored) {
return null;
}
});
}
public static @NotNull Set<String> getAllConverterIds() {
return converters.keySet();
}
public static @NotNull Set<String> getAllUsableConverterIds() {
return converters
.entrySet()
.stream()
.filter((entry) -> entry.getValue().canRunConverter())
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,76 @@
package de.oliver.fancyholograms.converter;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancylib.MessageHelper;
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<HologramData> 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())
);
}
}
}

View File

@@ -0,0 +1,44 @@
package de.oliver.fancyholograms.converter;
import de.oliver.fancyanalytics.api.events.Event;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.main.FancyHologramsPlugin;
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<HologramData> 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<HologramData> convert(@NotNull HologramConversionSession spec) {
List<HologramData> converted = convertHolograms(spec);
Event event = new Event("HologramsConverted")
.withProperty("converter", getId())
.withProperty("target", spec.getTarget().getRegex().pattern())
.withProperty("amount", String.valueOf(converted.size()));
FancyHologramsPlugin.get().getMetrics().getFancyAnalytics().sendEvent(event);
return converted;
}
public @NotNull List<String> getConvertableHolograms() {
return List.of();
}
}

Some files were not shown because too many files have changed in this diff Show More