diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/MinecraftVersion.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/MinecraftVersion.java index 4fc0160ac6..c1f623e7a5 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/api/MinecraftVersion.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/MinecraftVersion.java @@ -43,6 +43,12 @@ public enum MinecraftVersion { */ MINECRAFT_1_19(19, "1.19.x"), + /** + * This constant represents Minecraft (Java Edition) Version 1.19.4 + * This minor update added display entities + */ + MINECRAFT_1_19_4(19, 4, "1.19.4"), + /** * This constant represents Minecraft (Java Edition) Version 1.20 * ("The Trails & Tales Update") diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/attributes/HologramOwner.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/attributes/HologramOwner.java index b14e32c35c..c21b4e016a 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/attributes/HologramOwner.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/attributes/HologramOwner.java @@ -14,52 +14,73 @@ /** * This {@link ItemAttribute} manages holograms. * - * @author TheBusyBiscuit + * @author TheBusyBiscuit, JustAHuman * * @see HologramProjector * @see HologramsService - * */ public interface HologramOwner extends ItemAttribute { + /** + * This returns the offset of the hologram as a {@link Vector}. + * This offset is applied to {@link Block#getLocation()} when spawning + * the hologram. + * + * @param block + * The {@link Block} which serves as the origin point + * + * @return The hologram offset + */ + @Nonnull + default Vector getHologramOffset(@Nonnull Block block) { + return Slimefun.getHologramsService().getDefaultOffset(); + } /** * This will update the hologram text for the given {@link Block}. * - * @param b + * @param block * The {@link Block} to which the hologram belongs * * @param text - * The nametag for the hologram + * The text for the hologram */ - default void updateHologram(@Nonnull Block b, @Nonnull String text) { - Location loc = b.getLocation().add(getHologramOffset(b)); - Slimefun.getHologramsService().setHologramLabel(loc, ChatColors.color(text)); + default void updateHologram(@Nonnull Block block, @Nonnull String text) { + Location location = block.getLocation(); + if (Slimefun.getTickerTask().isDeletedSoon(location)) { + return; + } + + Slimefun.getHologramsService().setHologramLabel(location.add(getHologramOffset(block)), ChatColors.color(text)); } /** - * This will remove the hologram for the given {@link Block}. - * - * @param b - * The {@link Block} to which the hologram blocks + * This will update the hologram text for the given {@link Block}. + * + * @param block + * The {@link Block} to which the hologram belongs + * + * @param offset + * The new offset for the hologram */ - default void removeHologram(@Nonnull Block b) { - Location loc = b.getLocation().add(getHologramOffset(b)); - Slimefun.getHologramsService().removeHologram(loc); + default void setOffset(@Nonnull Block block, @Nonnull Vector offset) { + Location location = block.getLocation(); + if (Slimefun.getTickerTask().isDeletedSoon(location)) { + return; + } + + Location hologramLocation = location.clone().add(getHologramOffset(block)); + Location newHologramLocation = location.clone().add(offset); + Slimefun.getHologramsService().teleportHologram(hologramLocation, newHologramLocation); } /** - * This returns the offset of the hologram as a {@link Vector}. - * This offset is applied to {@link Block#getLocation()} when spawning - * the hologram. + * This will remove the hologram for the given {@link Block}. * * @param block - * The {@link Block} which serves as the origin point - * - * @return The hologram offset + * The {@link Block} to which the hologram blocks */ - @Nonnull - default Vector getHologramOffset(@Nonnull Block block) { - return Slimefun.getHologramsService().getDefaultOffset(); + default void removeHologram(@Nonnull Block block) { + Location location = block.getLocation().add(getHologramOffset(block)); + Slimefun.getHologramsService().removeHologram(location); } - } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/ArmorStandHologram.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/ArmorStandHologram.java new file mode 100644 index 0000000000..cfbe0497ba --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/ArmorStandHologram.java @@ -0,0 +1,53 @@ +package io.github.thebusybiscuit.slimefun4.core.services.holograms; + +import io.github.bakedlibs.dough.blocks.BlockPosition; +import io.github.bakedlibs.dough.data.persistent.PersistentDataAPI; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.utils.ArmorStandUtils; +import org.bukkit.Location; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; + +import java.util.Objects; + +public class ArmorStandHologram extends Hologram { + private ArmorStandHologram(ArmorStand entity) { + super(entity.getUniqueId()); + } + + @Override + public void setText(String text) { + this.lastAccess = System.currentTimeMillis(); + if (Objects.equals(this.text, text)) { + return; + } + + Entity entity = getEntity(); + if (entity != null) { + entity.setCustomName(text); + entity.setCustomNameVisible(text != null); + } + } + + @Override + public Class getEntityType() { + return ArmorStand.class; + } + + static ArmorStandHologram of(Entity entity, BlockPosition position) { + if (!(entity instanceof ArmorStand armorStand)) { + return null; + } + + armorStand.setAI(false); + armorStand.setInvulnerable(true); + ArmorStandUtils.setupArmorStand(armorStand); + PersistentDataAPI.setLong(entity, Slimefun.getHologramsService().getKey(), position.getPosition()); + return new ArmorStandHologram(armorStand); + } + + static ArmorStandHologram create(Location location, BlockPosition position) { + ArmorStand armorStand = location.getWorld().spawn(location, ArmorStand.class); + return of(armorStand, position); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/Hologram.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/Hologram.java index b69e71d534..13b959dd68 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/Hologram.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/Hologram.java @@ -1,6 +1,5 @@ package io.github.thebusybiscuit.slimefun4.core.services.holograms; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -8,128 +7,89 @@ import javax.annotation.Nullable; import org.bukkit.Bukkit; -import org.bukkit.entity.ArmorStand; +import org.bukkit.Location; import org.bukkit.entity.Entity; /** - * This represents an {@link ArmorStand} that can expire and be renamed. + * A {@link Hologram} is a wrapper around an {@link Entity} + * for displaying text as "holograms". * - * @author TheBusyBiscuit - * + * @author TheBusyBiscuit, JustAHuman */ -class Hologram { - - /** - * This is the minimum duration after which the {@link Hologram} will expire. - */ +abstract class Hologram { private static final long EXPIRES_AFTER = TimeUnit.MINUTES.toMillis(10); - /** - * The {@link UUID} of the {@link ArmorStand}. - */ - private final UUID uniqueId; - - /** - * The timestamp of when the {@link ArmorStand} was last accessed. - */ - private long lastAccess; - - /** - * The label of this {@link Hologram}. - */ - private String label; + protected final UUID uniqueId; + protected long lastAccess; + protected String text; /** * This creates a new {@link Hologram} for the given {@link UUID}. * * @param uniqueId - * The {@link UUID} of the corresponding {@link ArmorStand} + * The {@link UUID} of the corresponding {@link Entity} */ - Hologram(@Nonnull UUID uniqueId) { + protected Hologram(@Nonnull UUID uniqueId) { this.uniqueId = uniqueId; this.lastAccess = System.currentTimeMillis(); } - /** - * This returns the corresponding {@link ArmorStand} - * and also updates the "lastAccess" timestamp. - *

- * If the {@link ArmorStand} was removed, it will return null. - * - * @return The {@link ArmorStand} or null. - */ - @Nullable - ArmorStand getArmorStand() { - Entity n = Bukkit.getEntity(uniqueId); - - if (n instanceof ArmorStand armorStand && n.isValid()) { - this.lastAccess = System.currentTimeMillis(); - return armorStand; - } else { - this.lastAccess = 0; - return null; - } - } + public abstract void setText(@Nullable String text); + public abstract Class getEntityType(); /** - * This checks if the associated {@link ArmorStand} has despawned. - * - * @return Whether the {@link ArmorStand} despawned + * @return Whether the associated {@link Entity} has despawned. */ - boolean hasDespawned() { - return getArmorStand() == null; + public boolean hasDespawned() { + return getEntity() == null; } /** * This returns whether this {@link Hologram} has expired. - * The armorstand will expire if the last access has been more than 10 - * minutes ago. - * + * The {@link Hologram} has expired if the last access was + * more than 10 minutes ago. + * * @return Whether this {@link Hologram} has expired */ - boolean hasExpired() { + public boolean hasExpired() { return System.currentTimeMillis() - lastAccess > EXPIRES_AFTER; } /** - * This method sets the label of this {@link Hologram}. + * This returns the corresponding {@link Entity} + * and also updates the "lastAccess" timestamp. + *

+ * If the entity was removed, it will return null. * - * @param label - * The label to set + * @return The {@link Entity} or null. */ - void setLabel(@Nullable String label) { - if (Objects.equals(this.label, label)) { - /* - * Label is already set, no need to cause an entity - * update. But we can update the lastAccess flag. - */ + @Nullable + public Entity getEntity() { + Entity entity = Bukkit.getEntity(uniqueId); + if (getEntityType().isInstance(entity) && entity.isValid()) { this.lastAccess = System.currentTimeMillis(); + return getEntityType().cast(entity); } else { - this.label = label; - ArmorStand entity = getArmorStand(); + this.lastAccess = 0; + return null; + } + } - if (entity != null) { - if (label != null) { - entity.setCustomNameVisible(true); - entity.setCustomName(label); - } else { - entity.setCustomNameVisible(false); - entity.setCustomName(null); - } - } + public void teleport(Location location) { + Entity getEntity = getEntity(); + if (getEntity != null) { + getEntity.teleport(location); } } /** - * This will remove the {@link ArmorStand} and expire this {@link Hologram}. + * This will remove the {@link Entity} and expire this {@link Hologram}. */ - void remove() { - ArmorStand armorstand = getArmorStand(); - - if (armorstand != null) { + public void remove() { + Entity entity = getEntity(); + if (entity != null) { lastAccess = 0; - armorstand.remove(); + entity.remove(); } } - } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/HologramsService.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/HologramsService.java index 2673dd9b91..98e49db942 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/HologramsService.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/HologramsService.java @@ -2,7 +2,6 @@ import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.function.Consumer; import java.util.logging.Level; @@ -11,6 +10,8 @@ import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; +import io.github.bakedlibs.dough.data.persistent.PersistentDataAPI; +import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -19,8 +20,6 @@ import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; @@ -31,54 +30,25 @@ /** * This service is responsible for handling holograms. * - * @author TheBusyBiscuit + * @author TheBusyBiscuit, JustAHuman * + * @see Hologram * @see HologramOwner */ public class HologramsService { - - /** - * The radius in which we scan for holograms - */ - private static final double RADIUS = 0.45; - - /** - * The frequency at which to purge. - * Every 45 seconds. - */ + private static final double SCAN_RADIUS = 0.45; private static final long PURGE_RATE = 45L * 20L; + private static final Vector DEFAULT_OFFSET = new Vector(0.5, 0.75, 0.5); - /** - * Our {@link Plugin} instance - */ private final Plugin plugin; - - /** - * The default hologram offset - */ - private final Vector defaultOffset = new Vector(0.5, 0.75, 0.5); - - /** - * The {@link NamespacedKey} used to store data on a hologram - */ - private final NamespacedKey persistentDataKey; - - /** - * Our cache to save {@link Entity} lookups - */ + private final NamespacedKey key; private final Map cache = new HashMap<>(); + protected boolean started = false; - /** - * This constructs a new {@link HologramsService}. - * - * @param plugin - * Our {@link Plugin} instance - */ public HologramsService(@Nonnull Plugin plugin) { - this.plugin = plugin; - // Null-Validation is performed in the NamespacedKey constructor - persistentDataKey = new NamespacedKey(plugin, "hologram_id"); + this.plugin = plugin; + this.key = new NamespacedKey(plugin, "hologram_id"); } /** @@ -86,155 +56,128 @@ public HologramsService(@Nonnull Plugin plugin) { * purge-task. */ public void start() { + if (started) { + // TODO: Log a warning/what do reviewers think? + return; + } + plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, this::purge, PURGE_RATE, PURGE_RATE); } /** - * This returns the default {@link Hologram} offset. - * - * @return The default offset + * @return The default {@link Hologram} offset + */ + public @Nonnull Vector getDefaultOffset() { + return DEFAULT_OFFSET.clone(); + } + + /** + * @return The {@link NamespacedKey} marking an entity as a hologram & storing its position */ - @Nonnull - public Vector getDefaultOffset() { - return defaultOffset; + public @Nonnull NamespacedKey getKey() { + return key; } /** * This purges any expired {@link Hologram}. */ private void purge() { - Iterator iterator = cache.values().iterator(); - - while (iterator.hasNext()) { - Hologram hologram = iterator.next(); - - if (hologram.hasExpired()) { - iterator.remove(); - } - } + cache.values().removeIf(Hologram::hasExpired); } /** * This returns the {@link Hologram} associated with the given {@link Location}. - * If createIfNoneExists is set to true a new {@link ArmorStand} will be spawned + * If createIfNoneExists is set to true a new {@link Hologram} will be spawned * if no existing one could be found. * * @param loc * The {@link Location} * @param createIfNoneExists - * Whether to create a new {@link ArmorStand} if none was found + * Whether to create a new {@link Hologram} if none was found * * @return The existing (or newly created) hologram */ @Nullable private Hologram getHologram(@Nonnull Location loc, boolean createIfNoneExists) { Validate.notNull(loc, "Location cannot be null"); + Validate.notNull(loc.getWorld(), "The Location's World cannot be null"); BlockPosition position = new BlockPosition(loc); Hologram hologram = cache.get(position); - // Check if the ArmorStand was cached and still exists + // Check if the Hologram was cached and still exists if (hologram != null && !hologram.hasDespawned()) { return hologram; } // Scan all nearby entities which could be possible holograms - Collection holograms = loc.getWorld().getNearbyEntities(loc, RADIUS, RADIUS, RADIUS, this::isHologram); - - for (Entity n : holograms) { - if (n instanceof ArmorStand) { - PersistentDataContainer container = n.getPersistentDataContainer(); - - /* - * Any hologram we created will have a persistent data key for identification. - * Make sure that the value matches our BlockPosition. - */ - if (hasHologramData(container, position)) { - if (hologram != null) { - // Fixes #2927 - Remove any duplicates we find - n.remove(); - } else { - hologram = getAsHologram(position, n, container); - } - } + Collection holograms = loc.getWorld().getNearbyEntities(loc, SCAN_RADIUS, SCAN_RADIUS, SCAN_RADIUS, entity -> isHologram(entity, position)); + for (Entity entity : holograms) { + if (hologram == null) { + hologram = getAsHologram(entity, position); + } else { + // Fixes #2927 - Remove any duplicates we find + entity.remove(); } } if (hologram == null && createIfNoneExists) { - // Spawn a new ArmorStand - ArmorStand armorstand = (ArmorStand) loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND); - PersistentDataContainer container = armorstand.getPersistentDataContainer(); - - return getAsHologram(position, armorstand, container); + if (Slimefun.getMinecraftVersion().isAtLeast(MinecraftVersion.MINECRAFT_1_19_4)) { + return TextDisplayHologram.create(loc, position); + } + return ArmorStandHologram.create(loc, position); } else { return hologram; } } @ParametersAreNonnullByDefault - private boolean hasHologramData(PersistentDataContainer container, BlockPosition position) { - if (container.has(persistentDataKey, PersistentDataType.LONG)) { - long value = container.get(persistentDataKey, PersistentDataType.LONG); - return value == position.getPosition(); - } else { - return false; - } + private boolean hasHologramData(Entity entity, BlockPosition position) { + return PersistentDataAPI.getLong(entity, key) == position.getPosition(); } /** - * This checks if a given {@link Entity} is an {@link ArmorStand} - * and whether it has the correct attributes to be considered a {@link Hologram}. + * This checks if a given {@link Entity} is an is the right type of entity + * with the right properties to be a {@link Hologram}. * - * @param n + * @param entity * The {@link Entity} to check * * @return Whether this could be a hologram */ - private boolean isHologram(@Nonnull Entity n) { - if (n instanceof ArmorStand armorStand) { + private boolean isHologram(@Nonnull Entity entity, BlockPosition position) { + if (entity instanceof ArmorStand armorStand) { // The absolute minimum requirements to count as a hologram - return !armorStand.isVisible() && armorStand.isSilent() && !armorStand.hasGravity(); - } else { - return false; + return !armorStand.isVisible() + && armorStand.isSilent() + && !armorStand.hasGravity() + && hasHologramData(armorStand, position); + } else if (Slimefun.getMinecraftVersion().isAtLeast(MinecraftVersion.MINECRAFT_1_19_4) && entity.getType() == EntityType.TEXT_DISPLAY) { + return hasHologramData(entity, position); } + return false; } /** - * This will cast the {@link Entity} to an {@link ArmorStand} and it will apply - * all necessary attributes to the {@link ArmorStand}, then return a {@link Hologram}. - * + * This will cast and find the matching {@link Hologram} for the given {@link Entity}. + * + * @param entity + * * The {@link Entity} * @param position * The {@link BlockPosition} of this hologram - * @param entity - * The {@link Entity} - * @param container - * The {@link PersistentDataContainer} of the given {@link Entity} * * @return The {@link Hologram} */ - @Nullable - private Hologram getAsHologram(@Nonnull BlockPosition position, @Nonnull Entity entity, @Nonnull PersistentDataContainer container) { - if (entity instanceof ArmorStand armorStand) { - armorStand.setVisible(false); - armorStand.setInvulnerable(true); - armorStand.setSilent(true); - armorStand.setMarker(true); - armorStand.setAI(false); - armorStand.setGravity(false); - armorStand.setRemoveWhenFarAway(false); - - // Set a persistent tag to re-identify the correct hologram later - container.set(persistentDataKey, PersistentDataType.LONG, position.getPosition()); - - // Store in cache for faster access - Hologram hologram = new Hologram(armorStand.getUniqueId()); - cache.put(position, hologram); - - return hologram; - } else { - // This should never be reached - return null; + @ParametersAreNonnullByDefault + private @Nullable Hologram getAsHologram(Entity entity, BlockPosition position) { + Hologram hologram = null; + if (Slimefun.getMinecraftVersion().isAtLeast(MinecraftVersion.MINECRAFT_1_19_4)) { + hologram = TextDisplayHologram.of(entity, position); } + if (hologram == null) { + hologram = ArmorStandHologram.of(entity, position); + } + return hologram; } /** @@ -251,74 +194,80 @@ private Hologram getAsHologram(@Nonnull BlockPosition position, @Nonnull Entity private void updateHologram(@Nonnull Location loc, @Nonnull Consumer consumer) { Validate.notNull(loc, "Location must not be null"); Validate.notNull(consumer, "Callbacks must not be null"); + if (!Bukkit.isPrimaryThread()) { + Slimefun.runSync(() -> updateHologram(loc, consumer)); + return; + } - Runnable runnable = () -> { - try { - Hologram hologram = getHologram(loc, true); - - if (hologram != null) { - consumer.accept(hologram); - } - } catch (Exception | LinkageError x) { - Slimefun.logger().log(Level.SEVERE, "Hologram located at {0}", new BlockPosition(loc)); - Slimefun.logger().log(Level.SEVERE, "Something went wrong while trying to update this hologram", x); + try { + Hologram hologram = getHologram(loc, true); + if (hologram != null) { + consumer.accept(hologram); } - }; - - if (Bukkit.isPrimaryThread()) { - runnable.run(); - } else { - Slimefun.runSync(runnable); + } catch (Exception | LinkageError x) { + Slimefun.logger().log(Level.SEVERE, "Hologram located at {0}", new BlockPosition(loc)); + Slimefun.logger().log(Level.SEVERE, "Something went wrong while trying to update this hologram", x); } } + /** + * This will update the text of the {@link Hologram}. + * + * @param location + * The {@link Location} of this {@link Hologram} + * @param text + * The text to set, can be null + */ + public void setHologramLabel(@Nonnull Location location, @Nullable String text) { + Validate.notNull(location, "Location must not be null"); + + updateHologram(location, hologram -> hologram.setText(text)); + } + + /** + * This will teleport the {@link Hologram} to the given {@link Location}. + * + * @param location + * The {@link Location} of this {@link Hologram} + * @param to + * The {@link Location} to teleport the {@link Hologram} to + */ + public void teleportHologram(@Nonnull Location location, @Nonnull Location to) { + Validate.notNull(location, "Location must not be null"); + + updateHologram(location, hologram -> hologram.teleport(to)); + } + /** * This removes the {@link Hologram} at that given {@link Location}. *

* This method must be executed on the main {@link Server} {@link Thread}. - * - * @param loc + * + * @param location * The {@link Location} - * + * * @return Whether the {@link Hologram} could be removed, false if the {@link Hologram} does not * exist or was already removed */ - public boolean removeHologram(@Nonnull Location loc) { - Validate.notNull(loc, "Location cannot be null"); - - if (Bukkit.isPrimaryThread()) { - try { - Hologram hologram = getHologram(loc, false); - - if (hologram != null) { - cache.remove(new BlockPosition(loc)); - hologram.remove(); - return true; - } else { - return false; - } - } catch (Exception | LinkageError x) { - Slimefun.logger().log(Level.SEVERE, "Hologram located at {0}", new BlockPosition(loc)); - Slimefun.logger().log(Level.SEVERE, "Something went wrong while trying to remove this hologram", x); - return false; - } - } else { + public boolean removeHologram(@Nonnull Location location) { + Validate.notNull(location, "Location cannot be null"); + if (!Bukkit.isPrimaryThread()) { throw new UnsupportedOperationException("You cannot remove a hologram asynchronously."); } - } - /** - * This will update the label of the {@link Hologram}. - * - * @param loc - * The {@link Location} of this {@link Hologram} - * @param label - * The label to set, can be null - */ - public void setHologramLabel(@Nonnull Location loc, @Nullable String label) { - Validate.notNull(loc, "Location must not be null"); + try { + Hologram hologram = getHologram(location, false); + if (hologram == null) { + return false; + } - updateHologram(loc, hologram -> hologram.setLabel(label)); + cache.remove(new BlockPosition(location)); + hologram.remove(); + return true; + } catch (Exception | LinkageError x) { + Slimefun.logger().log(Level.SEVERE, "Hologram located at {0}", new BlockPosition(location)); + Slimefun.logger().log(Level.SEVERE, "Something went wrong while trying to remove this hologram", x); + return false; + } } - } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/TextDisplayHologram.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/TextDisplayHologram.java new file mode 100644 index 0000000000..7b7e691e11 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/TextDisplayHologram.java @@ -0,0 +1,50 @@ +package io.github.thebusybiscuit.slimefun4.core.services.holograms; + +import io.github.bakedlibs.dough.blocks.BlockPosition; +import io.github.bakedlibs.dough.data.persistent.PersistentDataAPI; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.TextDisplay; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +public class TextDisplayHologram extends Hologram { + private TextDisplayHologram(@Nonnull TextDisplay textDisplay) { + super(textDisplay.getUniqueId()); + } + + @Override + public void setText(@Nullable String text) { + this.lastAccess = System.currentTimeMillis(); + if (Objects.equals(this.text, text)) { + return; + } + + Entity entity = getEntity(); + if (entity instanceof TextDisplay textDisplay) { + textDisplay.setText(text); + } + } + + @Override + public Class getEntityType() { + return TextDisplay.class; + } + + static TextDisplayHologram of(Entity entity, BlockPosition position) { + if (!(entity instanceof TextDisplay textDisplay)) { + return null; + } + + PersistentDataAPI.setLong(entity, Slimefun.getHologramsService().getKey(), position.getPosition()); + return new TextDisplayHologram(textDisplay); + } + + static TextDisplayHologram create(Location location, BlockPosition position) { + TextDisplay textDisplay = location.getWorld().spawn(location, TextDisplay.class); + return of(textDisplay, position); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/blocks/HologramProjector.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/blocks/HologramProjector.java index 21891d293a..52bd502b6c 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/blocks/HologramProjector.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/blocks/HologramProjector.java @@ -3,11 +3,8 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; -import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.ItemStack; @@ -25,12 +22,12 @@ import io.github.thebusybiscuit.slimefun4.core.services.holograms.HologramsService; import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; import io.github.thebusybiscuit.slimefun4.implementation.handlers.SimpleBlockBreakHandler; -import io.github.thebusybiscuit.slimefun4.utils.ArmorStandUtils; import io.github.thebusybiscuit.slimefun4.utils.ChatUtils; import io.github.thebusybiscuit.slimefun4.utils.NumberUtils; import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.ChestMenu; import me.mrCookieSlime.Slimefun.api.BlockStorage; +import org.bukkit.util.Vector; /** * The {@link HologramProjector} is a very simple block which allows the {@link Player} @@ -42,10 +39,8 @@ * * @see HologramOwner * @see HologramsService - * */ public class HologramProjector extends SlimefunItem implements HologramOwner { - private static final String OFFSET_PARAMETER = "offset"; @ParametersAreNonnullByDefault @@ -57,47 +52,55 @@ public HologramProjector(ItemGroup itemGroup, SlimefunItemStack item, RecipeType private @Nonnull BlockPlaceHandler onPlace() { return new BlockPlaceHandler(false) { - @Override - public void onPlayerPlace(BlockPlaceEvent e) { - Block b = e.getBlockPlaced(); - BlockStorage.addBlockInfo(b, "text", "Edit me via the Projector"); - BlockStorage.addBlockInfo(b, OFFSET_PARAMETER, "0.5"); - BlockStorage.addBlockInfo(b, "owner", e.getPlayer().getUniqueId().toString()); - - getArmorStand(b, true); + public void onPlayerPlace(@Nonnull BlockPlaceEvent event) { + Block block = event.getBlockPlaced(); + BlockStorage.addBlockInfo(block, "text", "Edit me via the Projector"); + BlockStorage.addBlockInfo(block, OFFSET_PARAMETER, "0.5"); + BlockStorage.addBlockInfo(block, "owner", event.getPlayer().getUniqueId().toString()); + updateHologram(block, "Edit me via the Projector"); } - }; } private @Nonnull BlockBreakHandler onBreak() { return new SimpleBlockBreakHandler() { - @Override - public void onBlockBreak(@Nonnull Block b) { - killArmorStand(b); + public void onBlockBreak(@Nonnull Block block) { + removeHologram(block); } }; } - public @Nonnull BlockUseHandler onRightClick() { - return e -> { - e.cancel(); - - Player p = e.getPlayer(); - Block b = e.getClickedBlock().get(); + private @Nonnull BlockUseHandler onRightClick() { + return event -> { + event.cancel(); - if (BlockStorage.getLocationInfo(b.getLocation(), "owner").equals(p.getUniqueId().toString())) { - openEditor(p, b); + Player player = event.getPlayer(); + Block block = event.getClickedBlock().get(); + if (BlockStorage.getLocationInfo(block.getLocation(), "owner").equals(player.getUniqueId().toString())) { + openEditor(player, block); } }; } + public double getOffset(@Nonnull Block projector) { + return NumberUtils.reparseDouble(Double.parseDouble(BlockStorage.getLocationInfo(projector.getLocation(), OFFSET_PARAMETER))); + } + + public String getText(@Nonnull Block projector) { + return BlockStorage.getLocationInfo(projector.getLocation(), "text"); + } + + @Override + public @Nonnull Vector getHologramOffset(@Nonnull Block projector) { + return new Vector(0.5, getOffset(projector), 0.5); + } + private void openEditor(@Nonnull Player p, @Nonnull Block projector) { ChestMenu menu = new ChestMenu(Slimefun.getLocalization().getMessage(p, "machines.HOLOGRAM_PROJECTOR.inventory-title")); - menu.addItem(0, new CustomItemStack(Material.NAME_TAG, "&7Text &e(Click to edit)", "", "&f" + ChatColors.color(BlockStorage.getLocationInfo(projector.getLocation(), "text")))); + menu.addItem(0, new CustomItemStack(Material.NAME_TAG, "&7Text &e(Click to edit)", "", "&f" + getText(projector))); menu.addMenuClickHandler(0, (pl, slot, item, action) -> { pl.closeInventory(); Slimefun.getLocalization().sendMessage(pl, "machines.HOLOGRAM_PROJECTOR.enter-text", true); @@ -106,61 +109,29 @@ private void openEditor(@Nonnull Player p, @Nonnull Block projector) { // Fixes #3445 - Make sure the projector is not broken if (!BlockStorage.check(projector, getId())) { // Hologram projector no longer exists. - // TODO: Add a chat message informing the player that their message was ignored. + Slimefun.getLocalization().sendMessage(pl, "machines.HOLOGRAM_PROJECTOR.broken", true); return; } - ArmorStand hologram = getArmorStand(projector, true); - hologram.setCustomName(ChatColors.color(message)); - BlockStorage.addBlockInfo(projector, "text", hologram.getCustomName()); + message = ChatColors.color(message); + updateHologram(projector, message); + BlockStorage.addBlockInfo(projector, "text", message); openEditor(pl, projector); }); return false; }); - menu.addItem(1, new CustomItemStack(Material.CLOCK, "&7Offset: &e" + NumberUtils.roundDecimalNumber(Double.valueOf(BlockStorage.getLocationInfo(projector.getLocation(), OFFSET_PARAMETER)) + 1.0D), "", "&fLeft Click: &7+0.1", "&fRight Click: &7-0.1")); + menu.addItem(1, new CustomItemStack(Material.CLOCK, "&7Offset: &e" + NumberUtils.roundDecimalNumber(getOffset(projector) + 1.0D), "", "&fLeft Click: &7+0.1", "&fRight Click: &7-0.1")); menu.addMenuClickHandler(1, (pl, slot, item, action) -> { - double offset = NumberUtils.reparseDouble(Double.valueOf(BlockStorage.getLocationInfo(projector.getLocation(), OFFSET_PARAMETER)) + (action.isRightClicked() ? -0.1F : 0.1F)); - ArmorStand hologram = getArmorStand(projector, true); - Location l = new Location(projector.getWorld(), projector.getX() + 0.5, projector.getY() + offset, projector.getZ() + 0.5); - hologram.teleport(l); - - BlockStorage.addBlockInfo(projector, OFFSET_PARAMETER, String.valueOf(offset)); + double offset = getOffset(projector) + (action.isRightClicked() ? -0.1F : 0.1F); + setOffset(projector, new Vector(0.5, offset, 0.5)); openEditor(pl, projector); + BlockStorage.addBlockInfo(projector, OFFSET_PARAMETER, String.valueOf(offset)); return false; }); menu.open(p); } - private static ArmorStand getArmorStand(@Nonnull Block projector, boolean createIfNoneExists) { - String nametag = BlockStorage.getLocationInfo(projector.getLocation(), "text"); - double offset = Double.parseDouble(BlockStorage.getLocationInfo(projector.getLocation(), OFFSET_PARAMETER)); - Location l = new Location(projector.getWorld(), projector.getX() + 0.5, projector.getY() + offset, projector.getZ() + 0.5); - - for (Entity n : l.getChunk().getEntities()) { - if (n instanceof ArmorStand armorStand && l.distanceSquared(n.getLocation()) < 0.4) { - String customName = n.getCustomName(); - - if (customName != null && customName.equals(nametag)) { - return armorStand; - } - } - } - - if (!createIfNoneExists) { - return null; - } - - return ArmorStandUtils.spawnArmorStand(l, nametag); - } - - private static void killArmorStand(@Nonnull Block b) { - ArmorStand hologram = getArmorStand(b, false); - - if (hologram != null) { - hologram.remove(); - } - } } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/utils/ArmorStandUtils.java b/src/main/java/io/github/thebusybiscuit/slimefun4/utils/ArmorStandUtils.java index e2cf5ae10f..48ba1fb1ce 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/utils/ArmorStandUtils.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/utils/ArmorStandUtils.java @@ -43,7 +43,7 @@ private ArmorStandUtils() {} /** * Spawns an {@link ArmorStand} at the given {@link Location} *
- * Set Properties: Invisible, Silent, Marker, No-Gravity, No Base Plate, Don't Remove When Far Away + * For set properties see {@link #setupArmorStand(ArmorStand)} * * @param location The {@link Location} to spawn the {@link ArmorStand} * @@ -63,7 +63,12 @@ private ArmorStandUtils() {} return location.getWorld().spawn(location, ArmorStand.class, ArmorStandUtils::setupArmorStand); } - private static void setupArmorStand(ArmorStand armorStand) { + /** + * Sets Invisible, Silent, Marker, No-Gravity, No Base Plate, Don't Remove When Far Away + * + * @param armorStand The {@link ArmorStand} to set up + */ + public static void setupArmorStand(ArmorStand armorStand) { armorStand.setVisible(false); armorStand.setSilent(true); armorStand.setMarker(true); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/utils/NumberUtils.java b/src/main/java/io/github/thebusybiscuit/slimefun4/utils/NumberUtils.java index 0b85e6e68d..1b1bf24fc8 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/utils/NumberUtils.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/utils/NumberUtils.java @@ -224,7 +224,7 @@ public static int getInt(@Nonnull String str, int defaultValue) { } public static double reparseDouble(double number) { - return Double.valueOf(roundDecimalNumber(number)); + return Double.parseDouble(roundDecimalNumber(number)); } public static long getLong(@Nullable Long value, long defaultValue) { diff --git a/src/main/resources/languages/en/messages.yml b/src/main/resources/languages/en/messages.yml index 77bd80895a..c1b84c73a8 100644 --- a/src/main/resources/languages/en/messages.yml +++ b/src/main/resources/languages/en/messages.yml @@ -316,6 +316,7 @@ machines: HOLOGRAM_PROJECTOR: enter-text: '&7Please enter your desired Hologram Text into your Chat. &r(Color Codes are supported!)' inventory-title: 'Hologram Editor' + broken: '&cThe Hologram Projector you were editing has been broken, your message has been ignored.' ELEVATOR: no-destinations: '&4No destinations found' diff --git a/src/test/resources/biomes/1.19.4.json b/src/test/resources/biomes/1.19.4.json new file mode 100644 index 0000000000..65022a47a0 --- /dev/null +++ b/src/test/resources/biomes/1.19.4.json @@ -0,0 +1,65 @@ +[ + "minecraft:ocean", + "minecraft:plains", + "minecraft:desert", + "minecraft:windswept_hills", + "minecraft:forest", + "minecraft:taiga", + "minecraft:swamp", + "minecraft:mangrove_swamp", + "minecraft:river", + "minecraft:nether_wastes", + "minecraft:the_end", + "minecraft:frozen_ocean", + "minecraft:frozen_river", + "minecraft:snowy_plains", + "minecraft:mushroom_fields", + "minecraft:beach", + "minecraft:jungle", + "minecraft:sparse_jungle", + "minecraft:deep_ocean", + "minecraft:stony_shore", + "minecraft:snowy_beach", + "minecraft:birch_forest", + "minecraft:dark_forest", + "minecraft:snowy_taiga", + "minecraft:old_growth_pine_taiga", + "minecraft:windswept_forest", + "minecraft:savanna", + "minecraft:savanna_plateau", + "minecraft:badlands", + "minecraft:wooded_badlands", + "minecraft:small_end_islands", + "minecraft:end_midlands", + "minecraft:end_highlands", + "minecraft:end_barrens", + "minecraft:warm_ocean", + "minecraft:lukewarm_ocean", + "minecraft:cold_ocean", + "minecraft:deep_lukewarm_ocean", + "minecraft:deep_cold_ocean", + "minecraft:deep_frozen_ocean", + "minecraft:the_void", + "minecraft:sunflower_plains", + "minecraft:windswept_gravelly_hills", + "minecraft:flower_forest", + "minecraft:ice_spikes", + "minecraft:old_growth_birch_forest", + "minecraft:old_growth_spruce_taiga", + "minecraft:windswept_savanna", + "minecraft:eroded_badlands", + "minecraft:bamboo_jungle", + "minecraft:soul_sand_valley", + "minecraft:crimson_forest", + "minecraft:warped_forest", + "minecraft:basalt_deltas", + "minecraft:dripstone_caves", + "minecraft:lush_caves", + "minecraft:deep_dark", + "minecraft:meadow", + "minecraft:grove", + "minecraft:snowy_slopes", + "minecraft:frozen_peaks", + "minecraft:jagged_peaks", + "minecraft:stony_peaks" +] \ No newline at end of file