Add FancyHolograms sources

This commit is contained in:
Oliver
2025-03-29 20:23:00 +01:00
parent 4c13ca94e1
commit 212e5d90b3
103 changed files with 8788 additions and 8 deletions

View File

@@ -0,0 +1,71 @@
plugins {
id("java-library")
id("maven-publish")
id("com.gradleup.shadow")
}
val minecraftVersion = "1.19.4"
dependencies {
compileOnly("io.papermc.paper:paper-api:$minecraftVersion-R0.1-SNAPSHOT")
compileOnly("de.oliver:FancyLib:36")
compileOnly("de.oliver.FancyAnalytics:logger:0.0.6")
implementation("org.lushplugins:ChatColorHandler:5.1.2")
}
tasks {
shadowJar {
relocate("org.lushplugins.chatcolorhandler", "de.oliver.fancyholograms.libs.chatcolorhandler")
archiveClassifier.set("")
}
publishing {
repositories {
maven {
name = "fancypluginsReleases"
url = uri("https://repo.fancyplugins.de/releases")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<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 = "de.oliver"
artifactId = "FancyHolograms"
version = project.version.toString()
from(project.components["java"])
}
}
}
java {
withSourcesJar()
withJavadocJar()
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(17)
}
}

View File

@@ -0,0 +1,91 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.ScheduledExecutorService;
public interface FancyHologramsPlugin {
static FancyHologramsPlugin get() {
if (isEnabled()) {
return EnabledChecker.getPlugin();
}
throw new NullPointerException("Plugin is not enabled");
}
static boolean isEnabled() {
return EnabledChecker.isFancyHologramsEnabled();
}
JavaPlugin getPlugin();
ExtendedFancyLogger getFancyLogger();
HologramManager getHologramManager();
/**
* Returns the configuration of the plugin.
*
* @return The configuration.
*/
HologramConfiguration getHologramConfiguration();
/**
* Sets the configuration of the plugin.
*
* @param configuration The new configuration.
* @param reload Whether the configuration should be reloaded.
*/
void setHologramConfiguration(HologramConfiguration configuration, boolean reload);
/**
* @return The hologram storage.
*/
HologramStorage getHologramStorage();
/**
* @return The hologram thread
*/
ScheduledExecutorService getHologramThread();
/**
* Sets the hologram storage.
*
* @param storage The new hologram storage.
* @param reload Whether the current hologram cache should be reloaded.
*/
void setHologramStorage(HologramStorage storage, boolean reload);
class EnabledChecker {
private static Boolean enabled;
private static FancyHologramsPlugin plugin;
public static Boolean isFancyHologramsEnabled() {
if (enabled != null) return enabled;
Plugin pl = Bukkit.getPluginManager().getPlugin("FancyHolograms");
if (pl != null && pl.isEnabled()) {
try {
plugin = (FancyHologramsPlugin) pl;
} catch (ClassCastException e) {
throw new IllegalStateException("API failed to access plugin, if using the FancyHolograms API make sure to set the dependency to compile only.");
}
enabled = true;
return true;
}
return false;
}
public static FancyHologramsPlugin getPlugin() {
return plugin;
}
}
}

View File

@@ -0,0 +1,69 @@
package de.oliver.fancyholograms.api;
import org.jetbrains.annotations.NotNull;
public interface HologramConfiguration {
/**
* Reloads the configuration.
*
* @param plugin The plugin instance.
*/
void reload(@NotNull FancyHologramsPlugin plugin);
/**
* Returns whether version notifications are muted.
*
* @return {@code true} if version notifications are muted, {@code false} otherwise.
*/
boolean areVersionNotificationsMuted();
/**
* Returns whether autosave is enabled.
*
* @return {@code true} if autosave is enabled, {@code false} otherwise.
*/
boolean isAutosaveEnabled();
/**
* Returns the interval at which autosave is performed.
*
* @return The autosave interval in minutes.
*/
int getAutosaveInterval();
/**
* Returns whether the plugin should save holograms when they are changed.
*
* @return {@code true} if the plugin should save holograms when they are changed, {@code false} otherwise.
*/
boolean isSaveOnChangedEnabled();
/**
* Returns the default visibility distance for holograms.
*
* @return The default hologram visibility distance.
*/
int getDefaultVisibilityDistance();
/**
* Returns whether the plugin should register its commands.
*
* @return {@code true} if the plugin should register its commands, {@code false} otherwise.
*/
boolean isRegisterCommands();
/**
* Returns the log level for the plugin.
*
* @return The log level for the plugin.
*/
String getLogLevel();
/**
* Returns the interval at which hologram visibility is updated.
*
* @return The hologram visibility update interval in milliseconds.
*/
int getUpdateVisibilityInterval();
}

View File

@@ -0,0 +1,29 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.api.hologram.Hologram;
import java.util.Collection;
import java.util.Optional;
public interface HologramManager {
Optional<Hologram> getHologram(String name);
Collection<Hologram> getPersistentHolograms();
Collection<Hologram> getHolograms();
void addHologram(Hologram hologram);
void removeHologram(Hologram hologram);
Hologram create(HologramData hologramData);
void loadHolograms();
void saveHolograms();
void reloadHolograms();
}

View File

@@ -0,0 +1,45 @@
package de.oliver.fancyholograms.api;
import de.oliver.fancyholograms.api.hologram.Hologram;
import java.util.Collection;
public interface HologramStorage {
/**
* Saves a collection of holograms.
*
* @param holograms The holograms to save.
* @param override Whether to override existing holograms.
*/
void saveBatch(Collection<Hologram> holograms, boolean override);
/**
* Saves a hologram.
*
* @param hologram The hologram to save.
*/
void save(Hologram hologram);
/**
* Deletes a hologram.
*
* @param hologram The hologram to delete.
*/
void delete(Hologram hologram);
/**
* Loads all holograms from all worlds
*
* @return A collection of all loaded holograms.
*/
Collection<Hologram> loadAll();
/**
* Loads all holograms from a specific world
*
* @param world The world to load the holograms from.
* @return A collection of all loaded holograms.
*/
Collection<Hologram> loadAll(String world);
}

View File

@@ -0,0 +1,72 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.hologram.HologramType;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import java.util.Objects;
public class BlockHologramData extends DisplayHologramData {
public static Material DEFAULT_BLOCK = Material.GRASS_BLOCK;
private Material block = DEFAULT_BLOCK;
/**
* @param name Name of hologram
* @param location Location of hologram
* Default values are already set
*/
public BlockHologramData(String name, Location location) {
super(name, HologramType.BLOCK, location);
}
public Material getBlock() {
return block;
}
public BlockHologramData setBlock(Material block) {
if (!Objects.equals(this.block, block)) {
this.block = block;
setHasChanges(true);
}
return this;
}
@Override
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
block = Material.getMaterial(section.getString("block", "GRASS_BLOCK").toUpperCase());
return true;
}
@Override
public boolean write(ConfigurationSection section, String name) {
super.write(section, name);
section.set("block", block.name());
return true;
}
@Override
public BlockHologramData copy(String name) {
BlockHologramData blockHologramData = new BlockHologramData(name, getLocation());
blockHologramData
.setBlock(this.getBlock())
.setScale(this.getScale())
.setShadowRadius(this.getShadowRadius())
.setShadowStrength(this.getShadowStrength())
.setBillboard(this.getBillboard())
.setTranslation(this.getTranslation())
.setBrightness(this.getBrightness())
.setVisibilityDistance(getVisibilityDistance())
.setVisibility(this.getVisibility())
.setPersistent(this.isPersistent())
.setLinkedNpcName(getLinkedNpcName());
return blockHologramData;
}
}

View File

@@ -0,0 +1,211 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.hologram.HologramType;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Display;
import org.jetbrains.annotations.ApiStatus;
import org.joml.Vector3f;
import java.util.Locale;
import java.util.Objects;
public class DisplayHologramData extends HologramData {
public static final Display.Billboard DEFAULT_BILLBOARD = Display.Billboard.CENTER;
public static final Vector3f DEFAULT_SCALE = new Vector3f(1, 1, 1);
public static final Vector3f DEFAULT_TRANSLATION = new Vector3f(0, 0, 0);
public static final float DEFAULT_SHADOW_RADIUS = 0.0f;
public static final float DEFAULT_SHADOW_STRENGTH = 1.0f;
public static final int DEFAULT_INTERPOLATION_DURATION = 0;
private Display.Billboard billboard = DEFAULT_BILLBOARD;
private Vector3f scale = new Vector3f(DEFAULT_SCALE);
private Vector3f translation = new Vector3f(DEFAULT_TRANSLATION);
private Display.Brightness brightness;
private float shadowRadius = DEFAULT_SHADOW_RADIUS;
private float shadowStrength = DEFAULT_SHADOW_STRENGTH;
private int interpolationDuration = DEFAULT_INTERPOLATION_DURATION;
/**
* @param name Name of hologram
* @param type Type of hologram
* @param location Location of hologram
* Default values are already set
*/
public DisplayHologramData(String name, HologramType type, Location location) {
super(name, type, location);
}
public Display.Billboard getBillboard() {
return billboard;
}
public DisplayHologramData setBillboard(Display.Billboard billboard) {
if (!Objects.equals(this.billboard, billboard)) {
this.billboard = billboard;
setHasChanges(true);
}
return this;
}
public Vector3f getScale() {
return scale;
}
public DisplayHologramData setScale(Vector3f scale) {
if (!Objects.equals(this.scale, scale)) {
this.scale = scale;
setHasChanges(true);
}
return this;
}
public Vector3f getTranslation() {
return translation;
}
public DisplayHologramData setTranslation(Vector3f translation) {
if (!Objects.equals(this.translation, translation)) {
this.translation = translation;
setHasChanges(true);
}
return this;
}
public Display.Brightness getBrightness() {
return brightness;
}
public DisplayHologramData setBrightness(Display.Brightness brightness) {
if (!Objects.equals(this.brightness, brightness)) {
this.brightness = brightness;
setHasChanges(true);
}
return this;
}
public float getShadowRadius() {
return shadowRadius;
}
public DisplayHologramData setShadowRadius(float shadowRadius) {
if (this.shadowRadius != shadowRadius) {
this.shadowRadius = shadowRadius;
setHasChanges(true);
}
return this;
}
public float getShadowStrength() {
return shadowStrength;
}
public DisplayHologramData setShadowStrength(float shadowStrength) {
if (this.shadowStrength != shadowStrength) {
this.shadowStrength = shadowStrength;
setHasChanges(true);
}
return this;
}
@ApiStatus.Experimental
public int getInterpolationDuration() {
return interpolationDuration;
}
@ApiStatus.Experimental
public DisplayHologramData setInterpolationDuration(int interpolationDuration) {
if (this.interpolationDuration != interpolationDuration) {
this.interpolationDuration = interpolationDuration;
setHasChanges(true);
}
return this;
}
@Override
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
scale = new Vector3f(
(float) section.getDouble("scale_x", DEFAULT_SCALE.x),
(float) section.getDouble("scale_y", DEFAULT_SCALE.y),
(float) section.getDouble("scale_z", DEFAULT_SCALE.z)
);
translation = new Vector3f(
(float) section.getDouble("translation_x", DEFAULT_TRANSLATION.x),
(float) section.getDouble("translation_y", DEFAULT_TRANSLATION.y),
(float) section.getDouble("translation_z", DEFAULT_TRANSLATION.z)
);
shadowRadius = (float) section.getDouble("shadow_radius", DEFAULT_SHADOW_RADIUS);
shadowStrength = (float) section.getDouble("shadow_strength", DEFAULT_SHADOW_STRENGTH);
String billboardStr = section.getString("billboard", DEFAULT_BILLBOARD.name());
billboard = switch (billboardStr.toLowerCase()) {
case "fixed" -> Display.Billboard.FIXED;
case "vertical" -> Display.Billboard.VERTICAL;
case "horizontal" -> Display.Billboard.HORIZONTAL;
default -> Display.Billboard.CENTER;
};
int blockBrightness = Math.min(15, section.getInt("block_brightness", -1));
int skyBrightness = Math.min(15, section.getInt("sky_brightness", -1));
if(blockBrightness > -1 || skyBrightness > -1) {
brightness = new Display.Brightness(
Math.max(0, blockBrightness),
Math.max(0, skyBrightness)
);
}
return true;
}
@Override
public boolean write(ConfigurationSection section, String name) {
super.write(section, name);
section.set("scale_x", scale.x);
section.set("scale_y", scale.y);
section.set("scale_z", scale.z);
section.set("translation_x", translation.x);
section.set("translation_y", translation.y);
section.set("translation_z", translation.z);
section.set("shadow_radius", shadowRadius);
section.set("shadow_strength", shadowStrength);
if(brightness != null) {
section.set("block_brightness", brightness.getBlockLight());
section.set("sky_brightness", brightness.getSkyLight());
}
section.set("billboard", billboard != Display.Billboard.CENTER ? billboard.name().toLowerCase(Locale.ROOT) : null);
return true;
}
@Override
public DisplayHologramData copy(String name) {
DisplayHologramData displayHologramData = new DisplayHologramData(name, getType(), getLocation());
displayHologramData
.setScale(this.getScale())
.setShadowRadius(this.getShadowRadius())
.setShadowStrength(this.getShadowStrength())
.setBillboard(this.getBillboard())
.setTranslation(this.getTranslation())
.setBrightness(this.getBrightness())
.setVisibilityDistance(this.getVisibilityDistance())
.setVisibility(this.getVisibility())
.setPersistent(this.isPersistent())
.setLinkedNpcName(this.getLinkedNpcName());
return displayHologramData;
}
}

View File

@@ -0,0 +1,190 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.FancyHologramsPlugin;
import de.oliver.fancyholograms.api.data.property.Visibility;
import de.oliver.fancyholograms.api.hologram.HologramType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
public class HologramData implements YamlData {
public static final int DEFAULT_VISIBILITY_DISTANCE = -1;
public static final Visibility DEFAULT_VISIBILITY = Visibility.ALL;
public static final boolean DEFAULT_IS_VISIBLE = true;
public static final boolean DEFAULT_PERSISTENCE = true;
private final String name;
private final HologramType type;
private Location location;
private boolean hasChanges;
private int visibilityDistance = DEFAULT_VISIBILITY_DISTANCE;
private Visibility visibility = DEFAULT_VISIBILITY;
private boolean persistent = DEFAULT_PERSISTENCE;
private String linkedNpcName;
/**
* @param name Name of hologram
* @param type Type of hologram
* @param location Location of hologram
* Default values are already set
*/
public HologramData(String name, HologramType type, Location location) {
this.name = name;
this.type = type;
this.location = location;
}
public @NotNull String getName() {
return name;
}
public @NotNull HologramType getType() {
return type;
}
public @NotNull Location getLocation() {
return location.clone();
}
public HologramData setLocation(@Nullable Location location) {
this.location = location != null ? location.clone() : null;
setHasChanges(true);
return this;
}
/**
* @return Whether the hologram needs to send an update to players
*/
public final boolean hasChanges() {
return hasChanges;
}
/**
* @param hasChanges Whether the hologram needs to send an update to players
*/
public final void setHasChanges(boolean hasChanges) {
this.hasChanges = hasChanges;
}
public int getVisibilityDistance() {
if (visibilityDistance > 0) {
return visibilityDistance;
}
return FancyHologramsPlugin.get().getHologramConfiguration().getDefaultVisibilityDistance();
}
public HologramData setVisibilityDistance(int visibilityDistance) {
this.visibilityDistance = visibilityDistance;
setHasChanges(true);
return this;
}
/**
* Get the type of visibility for the hologram.
*
* @return type of visibility.
*/
public Visibility getVisibility() {
return this.visibility;
}
/**
* Set the type of visibility for the hologram.
*/
public HologramData setVisibility(@NotNull Visibility visibility) {
if (!Objects.equals(this.visibility, visibility)) {
this.visibility = visibility;
setHasChanges(true);
if (this.visibility.equals(Visibility.MANUAL)) {
Visibility.ManualVisibility.clear();
}
}
return this;
}
public boolean isPersistent() {
return persistent;
}
public HologramData setPersistent(boolean persistent) {
this.persistent = persistent;
return this;
}
public String getLinkedNpcName() {
return linkedNpcName;
}
public HologramData setLinkedNpcName(String linkedNpcName) {
if (!Objects.equals(this.linkedNpcName, linkedNpcName)) {
this.linkedNpcName = linkedNpcName;
setHasChanges(true);
}
return this;
}
@Override
public boolean read(ConfigurationSection section, String name) {
String worldName = section.getString("location.world", "world");
float x = (float) section.getDouble("location.x", 0);
float y = (float) section.getDouble("location.y", 0);
float z = (float) section.getDouble("location.z", 0);
float yaw = (float) section.getDouble("location.yaw", 0);
float pitch = (float) section.getDouble("location.pitch", 0);
World world = Bukkit.getWorld(worldName);
if (world == null) {
FancyHologramsPlugin.get().getFancyLogger().warn("Could not load hologram '" + name + "', because the world '" + worldName + "' is not loaded");
return false;
}
location = new Location(world, x, y, z, yaw, pitch);
visibilityDistance = section.getInt("visibility_distance", DEFAULT_VISIBILITY_DISTANCE);
visibility = Optional.ofNullable(section.getString("visibility"))
.flatMap(Visibility::byString)
.orElseGet(() -> {
final var visibleByDefault = section.getBoolean("visible_by_default", DisplayHologramData.DEFAULT_IS_VISIBLE);
return visibleByDefault ? Visibility.ALL : Visibility.PERMISSION_REQUIRED;
});
linkedNpcName = section.getString("linkedNpc");
return true;
}
@Override
public boolean write(ConfigurationSection section, String name) {
section.set("type", type.name());
section.set("location.world", location.getWorld().getName());
section.set("location.x", location.x());
section.set("location.y", location.y());
section.set("location.z", location.z());
section.set("location.yaw", location.getYaw());
section.set("location.pitch", location.getPitch());
section.set("visibility_distance", visibilityDistance);
section.set("visibility", visibility.name());
section.set("persistent", persistent);
section.set("linkedNpc", linkedNpcName);
return true;
}
public HologramData copy(String name) {
return new HologramData(name, type, this.getLocation())
.setVisibilityDistance(this.getVisibilityDistance())
.setVisibility(this.getVisibility())
.setPersistent(this.isPersistent())
.setLinkedNpcName(this.getLinkedNpcName());
}
}

View File

@@ -0,0 +1,73 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.hologram.HologramType;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import java.util.Objects;
public class ItemHologramData extends DisplayHologramData {
public static final ItemStack DEFAULT_ITEM = new ItemStack(Material.APPLE);
private ItemStack item = DEFAULT_ITEM;
/**
* @param name Name of hologram
* @param location Location of hologram
* Default values are already set
*/
public ItemHologramData(String name, Location location) {
super(name, HologramType.ITEM, location);
}
public ItemStack getItemStack() {
return item;
}
public ItemHologramData setItemStack(ItemStack item) {
if (!Objects.equals(this.item, item)) {
this.item = item;
setHasChanges(true);
}
return this;
}
@Override
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
item = section.getItemStack("item", DEFAULT_ITEM);
return true;
}
@Override
public boolean write(ConfigurationSection section, String name) {
super.write(section, name);
section.set("item", item);
return true;
}
@Override
public ItemHologramData copy(String name) {
ItemHologramData itemHologramData = new ItemHologramData(name, getLocation());
itemHologramData
.setItemStack(this.getItemStack())
.setScale(this.getScale())
.setShadowRadius(this.getShadowRadius())
.setShadowStrength(this.getShadowStrength())
.setBillboard(this.getBillboard())
.setTranslation(this.getTranslation())
.setBrightness(this.getBrightness())
.setVisibilityDistance(this.getVisibilityDistance())
.setVisibility(this.getVisibility())
.setPersistent(this.isPersistent())
.setLinkedNpcName(this.getLinkedNpcName());
return itemHologramData;
}
}

View File

@@ -0,0 +1,212 @@
package de.oliver.fancyholograms.api.data;
import de.oliver.fancyholograms.api.hologram.Hologram;
import de.oliver.fancyholograms.api.hologram.HologramType;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.TextDisplay;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public class TextHologramData extends DisplayHologramData {
public static final TextDisplay.TextAlignment DEFAULT_TEXT_ALIGNMENT = TextDisplay.TextAlignment.CENTER;
public static final boolean DEFAULT_TEXT_SHADOW_STATE = false;
public static final boolean DEFAULT_SEE_THROUGH = false;
public static final int DEFAULT_TEXT_UPDATE_INTERVAL = -1;
private List<String> text;
private Color background;
private TextDisplay.TextAlignment textAlignment = DEFAULT_TEXT_ALIGNMENT;
private boolean textShadow = DEFAULT_TEXT_SHADOW_STATE;
private boolean seeThrough = DEFAULT_SEE_THROUGH;
private int textUpdateInterval = DEFAULT_TEXT_UPDATE_INTERVAL;
/**
* @param name Name of hologram
* @param location Location of hologram
* Default values are already set
*/
public TextHologramData(String name, Location location) {
super(name, HologramType.TEXT, location);
text = new ArrayList<>(List.of("Edit this line with /hologram edit " + name));
}
public List<String> getText() {
return text;
}
public TextHologramData setText(List<String> text) {
if (!Objects.equals(this.text, text)) {
this.text = text;
setHasChanges(true);
}
return this;
}
public void addLine(String line) {
text.add(line);
setHasChanges(true);
}
public void removeLine(int index) {
text.remove(index);
setHasChanges(true);
}
public Color getBackground() {
return background;
}
public TextHologramData setBackground(Color background) {
if (!Objects.equals(this.background, background)) {
this.background = background;
setHasChanges(true);
}
return this;
}
public TextDisplay.TextAlignment getTextAlignment() {
return textAlignment;
}
public TextHologramData setTextAlignment(TextDisplay.TextAlignment textAlignment) {
if (!Objects.equals(this.textAlignment, textAlignment)) {
this.textAlignment = textAlignment;
setHasChanges(true);
}
return this;
}
public boolean hasTextShadow() {
return textShadow;
}
public TextHologramData setTextShadow(boolean textShadow) {
if (this.textShadow != textShadow) {
this.textShadow = textShadow;
setHasChanges(true);
}
return this;
}
public boolean isSeeThrough() {
return seeThrough;
}
public TextHologramData setSeeThrough(boolean seeThrough) {
if (this.seeThrough != seeThrough) {
this.seeThrough = seeThrough;
setHasChanges(true);
}
return this;
}
public int getTextUpdateInterval() {
return textUpdateInterval;
}
public TextHologramData setTextUpdateInterval(int textUpdateInterval) {
if (this.textUpdateInterval != textUpdateInterval) {
this.textUpdateInterval = textUpdateInterval;
setHasChanges(true);
}
return this;
}
@Override
public boolean read(ConfigurationSection section, String name) {
super.read(section, name);
text = section.getStringList("text");
if (text.isEmpty()) {
text = List.of("Could not load hologram text");
//TODO: maybe return false here?
}
textShadow = section.getBoolean("text_shadow", DEFAULT_TEXT_SHADOW_STATE);
seeThrough = section.getBoolean("see_through", DEFAULT_SEE_THROUGH);
textUpdateInterval = section.getInt("update_text_interval", DEFAULT_TEXT_UPDATE_INTERVAL);
String textAlignmentStr = section.getString("text_alignment", DEFAULT_TEXT_ALIGNMENT.name().toLowerCase());
textAlignment = switch (textAlignmentStr.toLowerCase(Locale.ROOT)) {
case "right" -> TextDisplay.TextAlignment.RIGHT;
case "left" -> TextDisplay.TextAlignment.LEFT;
default -> TextDisplay.TextAlignment.CENTER;
};
background = null;
String backgroundStr = section.getString("background", null);
if (backgroundStr != null) {
if (backgroundStr.equalsIgnoreCase("transparent")) {
background = Hologram.TRANSPARENT;
} else if (backgroundStr.startsWith("#")) {
background = Color.fromARGB((int) Long.parseLong(backgroundStr.substring(1), 16));
//backwards compatibility, make rgb hex colors solid color -their alpha is 0 by default-
if (backgroundStr.length() == 7) background = background.setAlpha(255);
} else {
background = Color.fromARGB(NamedTextColor.NAMES.value(backgroundStr.toLowerCase(Locale.ROOT).trim().replace(' ', '_')).value() | 0xC8000000);
}
}
return true;
}
@Override
public boolean write(ConfigurationSection section, String name) {
super.write(section, name);
section.set("text", text);
section.set("text_shadow", textShadow);
section.set("see_through", seeThrough);
section.set("text_alignment", textAlignment.name().toLowerCase(Locale.ROOT));
section.set("update_text_interval", textUpdateInterval);
final String color;
if (background == null) {
color = null;
} else if (background == Hologram.TRANSPARENT) {
color = "transparent";
} else {
NamedTextColor named = background.getAlpha() == 255 ? NamedTextColor.namedColor(background.asRGB()) : null;
color = named != null ? named.toString() : '#' + Integer.toHexString(background.asARGB());
}
section.set("background", color);
return true;
}
@Override
public TextHologramData copy(String name) {
TextHologramData textHologramData = new TextHologramData(name, getLocation());
textHologramData
.setText(this.getText())
.setBackground(this.getBackground())
.setTextAlignment(this.getTextAlignment())
.setTextShadow(this.hasTextShadow())
.setSeeThrough(this.isSeeThrough())
.setTextUpdateInterval(this.getTextUpdateInterval())
.setScale(this.getScale())
.setShadowRadius(this.getShadowRadius())
.setShadowStrength(this.getShadowStrength())
.setBillboard(this.getBillboard())
.setTranslation(this.getTranslation())
.setBrightness(this.getBrightness())
.setVisibilityDistance(this.getVisibilityDistance())
.setVisibility(this.getVisibility())
.setPersistent(this.isPersistent())
.setLinkedNpcName(this.getLinkedNpcName());
return textHologramData;
}
}

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,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.getName(), player.getUniqueId());
}
public static void addDistantViewer(Hologram hologram, UUID uuid) {
addDistantViewer(hologram.getName(), uuid);
}
public static void addDistantViewer(String hologramName, UUID uuid) {
distantViewers.put(hologramName, uuid);
}
public static void removeDistantViewer(Hologram hologram, UUID uuid) {
removeDistantViewer(hologram.getName(), uuid);
}
public static void removeDistantViewer(String hologramName, UUID uuid) {
distantViewers.remove(hologramName, uuid);
}
public static void remove(Hologram hologram) {
remove(hologram.getName());
}
public static void remove(String hologramName) {
distantViewers.removeAll(hologramName);
}
public static void clear() {
distantViewers.clear();
}
}
}

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,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 hidden from a player
*/
public final class HologramHideEvent extends HologramEvent {
private static final HandlerList handlerList = new HandlerList();
@NotNull
private final Player player;
public HologramHideEvent(@NotNull final Hologram hologram, @NotNull final Player player) {
super(hologram, !Bukkit.isPrimaryThread());
this.player = player;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull Player getPlayer() {
return this.player;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

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 HologramShowEvent extends HologramEvent {
private static final HandlerList handlerList = new HandlerList();
@NotNull
private final Player player;
public HologramShowEvent(@NotNull final Hologram hologram, @NotNull final Player player) {
super(hologram, !Bukkit.isPrimaryThread());
this.player = player;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull Player getPlayer() {
return this.player;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

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,35 @@
package de.oliver.fancyholograms.api.events;
import com.google.common.collect.ImmutableList;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
public final class HologramsLoadedEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final ImmutableList<Hologram> holograms;
public HologramsLoadedEvent(@NotNull final ImmutableList<Hologram> holograms) {
super(!Bukkit.isPrimaryThread());
this.holograms = holograms;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull ImmutableList<Hologram> getManager() {
return this.holograms;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View File

@@ -0,0 +1,35 @@
package de.oliver.fancyholograms.api.events;
import com.google.common.collect.ImmutableList;
import de.oliver.fancyholograms.api.hologram.Hologram;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
public final class HologramsUnloadedEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final ImmutableList<Hologram> holograms;
public HologramsUnloadedEvent(@NotNull final ImmutableList<Hologram> holograms) {
super(!Bukkit.isPrimaryThread());
this.holograms = holograms;
}
public static HandlerList getHandlerList() {
return handlerList;
}
public @NotNull ImmutableList<Hologram> getManager() {
return this.holograms;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View File

@@ -0,0 +1,372 @@
package de.oliver.fancyholograms.api.hologram;
import de.oliver.fancyholograms.api.data.HologramData;
import de.oliver.fancyholograms.api.data.TextHologramData;
import de.oliver.fancyholograms.api.data.property.Visibility;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.World;
import org.bukkit.entity.Display;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lushplugins.chatcolorhandler.ModernChatColorHandler;
import java.util.*;
/**
* Abstract base class for creating, updating, and managing holograms.
* <p>
* This class provides the basic functionality needed to work with holograms
* across multiple versions of Minecraft. To create a hologram specific to a version of Minecraft,
* extend this class and implement the abstract methods.
* <p>
* Note that the specific way holograms are created, updated, and deleted
* will vary depending on the Minecraft version.
* <p>
* A Hologram object includes data about the hologram and maintains a set of players to whom the hologram is shown.
*/
public abstract class Hologram {
public static final int LINE_WIDTH = 1000;
public static final Color TRANSPARENT = Color.fromARGB(0);
protected static final int MINIMUM_PROTOCOL_VERSION = 762;
protected final @NotNull HologramData data;
/**
* Set of UUIDs of players to whom the hologram is currently shown.
*/
protected final @NotNull Set<UUID> viewers = new HashSet<>();
protected Hologram(@NotNull final HologramData data) {
this.data = data;
}
@NotNull
public String getName() {
return data.getName();
}
public final @NotNull HologramData getData() {
return this.data;
}
/**
* Returns the entity id of this hologram
* This id is for packet use only as the entity is not registered to the server
* @return entity id
*/
public abstract int getEntityId();
/**
* Returns the Display entity of this Hologram object.
* The entity is not registered in the world or server.
* Only use this method if you know what you're doing.
* <p>
* This method will return <code>null</code> in 1.20.5 and newer versions
*
* @return the Display entity of this Hologram object
*/
@ApiStatus.Internal
@Deprecated(forRemoval = true, since = "2.4.1")
public abstract @Nullable Display getDisplayEntity();
protected abstract void create();
protected abstract void delete();
protected abstract void update();
protected abstract boolean show(@NotNull final Player player);
protected abstract boolean hide(@NotNull final Player player);
protected abstract void refresh(@NotNull final Player player);
/**
* Create the hologram entity.
* Only run this if creating custom Hologram implementations as this is run in
* {@link de.oliver.fancyholograms.api.HologramManager#create(HologramData)}.
*/
public final void createHologram() {
create();
}
/**
* Deletes the hologram entity.
*/
public final void deleteHologram() {
delete();
}
/**
* Shows the hologram to a collection of players.
* Use {@link #forceShowHologram(Player)} if this hologram is not registered to the HologramManager.
*
* @param players The players to show the hologram to
*/
public final void showHologram(Collection<? extends Player> players) {
players.forEach(this::showHologram);
}
/**
* Shows the hologram to a player.
* Use {@link #forceShowHologram(Player)} if this hologram is not registered to the HologramManager.
*
* @param player The player to show the hologram to
*/
public final void showHologram(Player player) {
viewers.add(player.getUniqueId());
}
/**
* Forcefully shows the hologram to a player.
*
* @param player The player to show the hologram to
*/
public final void forceShowHologram(Player player) {
show(player);
if (this.getData().getVisibility().equals(Visibility.MANUAL)) {
Visibility.ManualVisibility.addDistantViewer(this, player.getUniqueId());
}
}
/**
* Hides the hologram from a collection of players.
* Use {@link #forceHideHologram(Player)} if this hologram is not registered to the HologramManager.
*
* @param players The players to hide the hologram from
*/
public final void hideHologram(Collection<? extends Player> players) {
players.forEach(this::hideHologram);
}
/**
* Hides the hologram from a player.
* Use {@link #forceHideHologram(Player)} if this hologram is not registered to the HologramManager.
*
* @param player The player to hide the hologram from
*/
public final void hideHologram(Player player) {
viewers.remove(player.getUniqueId());
}
/**
* Forcefully hides the hologram from a player.
*
* @param player The player to show the hologram to
*/
public final void forceHideHologram(Player player) {
hide(player);
if (this.getData().getVisibility().equals(Visibility.MANUAL)) {
Visibility.ManualVisibility.removeDistantViewer(this, player.getUniqueId());
}
}
/**
* Queues hologram to update and refresh for players.
*
* @deprecated in favour of {@link #queueUpdate()}
*/
@Deprecated(forRemoval = true)
public final void updateHologram() {
queueUpdate();
}
/**
* Queues hologram to update and refresh for players
* Use {@link #forceUpdate()} if this hologram is not registered to the HologramManager.
*/
public final void queueUpdate() {
data.setHasChanges(true);
}
/**
* Forcefully updates and refreshes hologram for players.
*/
public final void forceUpdate() {
update();
}
/**
* Refreshes the hologram for the players currently viewing it.
*/
public void refreshForViewers() {
final var players = getViewers()
.stream()
.map(Bukkit::getPlayer)
.toList();
refreshHologram(players);
}
/**
* Refreshes the hologram for players currently viewing it in the same world as the hologram.
*/
public void refreshForViewersInWorld() {
World world = data.getLocation().getWorld();
final var players = getViewers()
.stream()
.map(Bukkit::getPlayer)
.filter(player -> player != null && player.getWorld().equals(world))
.toList();
refreshHologram(players);
}
/**
* Refreshes the hologram's data for a player.
*
* @param player the player to refresh for
*/
public final void refreshHologram(@NotNull final Player player) {
refresh(player);
}
/**
* Refreshes the hologram's data for a collection of players.
*
* @param players the collection of players to refresh for
*/
public final void refreshHologram(@NotNull final Collection<? extends Player> players) {
players.forEach(this::refreshHologram);
}
/**
* @return a copy of the set of UUIDs of players currently viewing the hologram
*/
public final @NotNull Set<UUID> getViewers() {
return new HashSet<>(this.viewers);
}
/**
* @param player the player to check for
* @return whether the player is currently viewing the hologram
*/
public final boolean isViewer(@NotNull final Player player) {
return isViewer(player.getUniqueId());
}
/**
* @param player the uuid of the player to check for
* @return whether the player is currently viewing the hologram
*/
public final boolean isViewer(@NotNull final UUID player) {
return this.viewers.contains(player);
}
protected boolean shouldShowTo(@NotNull final Player player) {
if (!meetsVisibilityConditions(player)) {
return false;
}
return isWithinVisibilityDistance(player);
}
public boolean meetsVisibilityConditions(@NotNull final Player player) {
return this.getData().getVisibility().canSee(player, this);
}
public boolean isWithinVisibilityDistance(@NotNull final Player player) {
final var location = getData().getLocation();
if (!location.getWorld().equals(player.getWorld())) {
return false;
}
int visibilityDistance = data.getVisibilityDistance();
double distanceSquared = location.distanceSquared(player.getLocation());
return distanceSquared <= visibilityDistance * visibilityDistance;
}
/**
* Checks and updates the shown state for a player.
* If the hologram is shown and should not be, it hides it.
* If the hologram is not shown and should be, it shows it.
* Use {@link #forceUpdateShownStateFor(Player)} if this hologram is not registered to the HologramManager.
*
* @param player the player to check and update the shown state for
*/
public void updateShownStateFor(Player player) {
boolean isShown = isViewer(player);
boolean shouldBeShown = shouldShowTo(player);
if (isShown && !shouldBeShown) {
showHologram(player);
} else if (!isShown && shouldBeShown) {
hideHologram(player);
}
}
/**
* Checks and forcefully updates the shown state for a player.
* If the hologram is shown and should not be, it hides it.
* If the hologram is not shown and should be, it shows it.
*
* @param player the player to check and update the shown state for
*/
public void forceUpdateShownStateFor(Player player) {
boolean isShown = isViewer(player);
if (meetsVisibilityConditions(player)) {
if (isWithinVisibilityDistance(player)) {
// Ran if the player meets the visibility conditions and is within visibility distance
if (!isShown) {
show(player);
if (getData().getVisibility().equals(Visibility.MANUAL)) {
Visibility.ManualVisibility.removeDistantViewer(this, player.getUniqueId());
}
}
} else {
// Ran if the player meets the visibility conditions but is not within visibility distance
if (isShown) {
hide(player);
if (getData().getVisibility().equals(Visibility.MANUAL)) {
Visibility.ManualVisibility.addDistantViewer(this, player.getUniqueId());
}
}
}
} else {
// Ran if the player does not meet visibility conditions
if (isShown) {
hide(player);
}
}
}
/**
* Gets the text shown in the hologram. If a player is specified, placeholders in the text are replaced
* with their corresponding values for the player.
*
* @param player the player to get the placeholders for, or null if no placeholders should be replaced
* @return the text shown in the hologram
*/
public final Component getShownText(@Nullable final Player player) {
if (!(getData() instanceof TextHologramData textData)) {
return null;
}
var text = String.join("\n", textData.getText());
return ModernChatColorHandler.translate(text, player);
}
@Override
public final boolean equals(@Nullable final Object o) {
if (this == o) return true;
if (!(o instanceof Hologram that)) return false;
return Objects.equals(this.getData(), that.getData());
}
@Override
public final int hashCode() {
return Objects.hash(this.getData());
}
}

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