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 a1b539afd..ab18e61b3 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 @@ -1,11 +1,9 @@ package io.github.thebusybiscuit.slimefun4.core.attributes; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.bukkit.Location; import org.bukkit.block.Block; -import org.bukkit.entity.ArmorStand; import org.bukkit.util.Vector; import io.github.thebusybiscuit.cscorelib2.chat.ChatColors; @@ -35,17 +33,7 @@ public interface HologramOwner extends ItemAttribute { */ default void updateHologram(@Nonnull Block b, @Nonnull String text) { Location loc = b.getLocation().add(getHologramOffset()); - - SlimefunPlugin.getHologramsService().updateHologram(loc, hologram -> { - hologram.setCustomName(ChatColors.color(text)); - hologram.setCustomNameVisible(true); - }); - } - - @Nullable - default ArmorStand getHologram(@Nonnull Block b, boolean createIfNoneExists) { - Location loc = b.getLocation().add(getHologramOffset()); - return SlimefunPlugin.getHologramsService().getHologram(loc, createIfNoneExists); + SlimefunPlugin.getHologramsService().setHologramLabel(loc, ChatColors.color(text)); } /** diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/CachedArmorStand.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/CachedArmorStand.java deleted file mode 100644 index e49703e1f..000000000 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/CachedArmorStand.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.thebusybiscuit.slimefun4.core.services.holograms; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bukkit.Bukkit; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; - -/** - * This represents an {@link ArmorStand} or hologram that can expire. - * - * @author TheBusyBiscuit - * - */ -class CachedArmorStand { - - /** - * This is the minimum duration after which the {@link CachedArmorStand} will expire. - */ - 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; - - /** - * This creates a new {@link CachedArmorStand} for the given {@link UUID}. - * - * @param uniqueId - * The {@link UUID} of the corresponding {@link ArmorStand} - */ - public CachedArmorStand(@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 - public ArmorStand getArmorStand() { - Entity n = Bukkit.getEntity(uniqueId); - - if (n instanceof ArmorStand && n.isValid()) { - this.lastAccess = System.currentTimeMillis(); - return (ArmorStand) n; - } else { - this.lastAccess = 0; - return null; - } - } - - /** - * This returns whether this {@link CachedArmorStand} has expired. - * The armorstand will expire if the last access has been more than 10 - * minutes ago. - * - * @return Whether this {@link CachedArmorStand} has expired - */ - public boolean isExpired() { - return System.currentTimeMillis() - lastAccess > EXPIRES_AFTER; - } - -} 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 new file mode 100644 index 000000000..1a6db53c8 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/holograms/Hologram.java @@ -0,0 +1,136 @@ +package io.github.thebusybiscuit.slimefun4.core.services.holograms; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.bukkit.Bukkit; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; + +import com.google.common.base.Objects; + +/** + * This represents an {@link ArmorStand} that can expire and be renamed. + * + * @author TheBusyBiscuit + * + */ +class Hologram { + + /** + * This is the minimum duration after which the {@link Hologram} will expire. + */ + 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; + + /** + * This creates a new {@link Hologram} for the given {@link UUID}. + * + * @param uniqueId + * The {@link UUID} of the corresponding {@link ArmorStand} + */ + 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 && n.isValid()) { + this.lastAccess = System.currentTimeMillis(); + return (ArmorStand) n; + } else { + this.lastAccess = 0; + return null; + } + } + + /** + * This checks if the associated {@link ArmorStand} has despawned. + * + * @return Whether the {@link ArmorStand} despawned + */ + boolean hasDespawned() { + return getArmorStand() == null; + } + + /** + * This returns whether this {@link Hologram} has expired. + * The armorstand will expire if the last access has been more than 10 + * minutes ago. + * + * @return Whether this {@link Hologram} has expired + */ + boolean hasExpired() { + return System.currentTimeMillis() - lastAccess > EXPIRES_AFTER; + } + + /** + * This method sets the label of this {@link Hologram}. + * + * @param label + * The label to set + */ + void setLabel(@Nullable String label) { + if (Objects.equal(this.label, label)) { + /* + * Label is already set, no need to cause an entity + * update. But we can update the lastAccess flag. + */ + this.lastAccess = System.currentTimeMillis(); + } else { + this.label = label; + ArmorStand entity = getArmorStand(); + + if (entity != null) { + if (label != null) { + entity.setCustomNameVisible(true); + entity.setCustomName(label); + } else { + entity.setCustomNameVisible(false); + entity.setCustomName(null); + } + } + } + } + + /** + * This will remove the {@link ArmorStand} and expire this {@link Hologram}. + */ + void remove() { + ArmorStand armorstand = getArmorStand(); + + if (armorstand != null) { + lastAccess = 0; + armorstand.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 9fe906a7e..6777466c4 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 @@ -27,7 +27,6 @@ import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin; /** * This service is responsible for handling holograms. - * This includes error management when something goes wrong. * * @author TheBusyBiscuit * @@ -59,7 +58,7 @@ public class HologramsService { /** * Our cache to save {@link Entity} lookups */ - private final Map cache = new HashMap<>(); + private final Map cache = new HashMap<>(); /** * This constructs a new {@link HologramsService}. @@ -83,20 +82,20 @@ public class HologramsService { } /** - * This purges all expired {@link CachedArmorStand CachedArmorStands}. + * This purges all expired {@link Hologram CachedArmorStands}. */ private void purge() { - Iterator iterator = cache.values().iterator(); + Iterator iterator = cache.values().iterator(); while (iterator.hasNext()) { - if (iterator.next().isExpired()) { + if (iterator.next().hasExpired()) { iterator.remove(); } } } /** - * This returns the hologram associated with the given {@link Location}. + * 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 no existing one could be found. * @@ -108,24 +107,20 @@ public class HologramsService { * @return The existing (or newly created) hologram */ @Nullable - public ArmorStand getHologram(@Nonnull Location loc, boolean createIfNoneExists) { + private Hologram getHologram(@Nonnull Location loc, boolean createIfNoneExists) { Validate.notNull(loc, "Location cannot be null"); + // Make sure there's no concurrency issues if (!Bukkit.isPrimaryThread()) { throw new UnsupportedOperationException("A hologram cannot be accessed asynchronously."); } BlockPosition position = new BlockPosition(loc); - CachedArmorStand cachedEntity = cache.get(position); + Hologram hologram = cache.get(position); - // Check if the ArmorStand was cached - if (cachedEntity != null) { - ArmorStand armorstand = cachedEntity.getArmorStand(); - - // If the Entity still exists, return it - if (armorstand != null) { - return armorstand; - } + // Check if the ArmorStand was cached and still exists + if (hologram != null && hologram.hasDespawned()) { + return hologram; } // Scan all nearby entities which could be possible holograms @@ -138,52 +133,134 @@ public class HologramsService { if (container.has(persistentDataKey, PersistentDataType.LONG)) { // Check if it is ours or a different one. if (container.get(persistentDataKey, PersistentDataType.LONG).equals(position.getPosition())) { - return (ArmorStand) n; + return getAsHologram(position, n, container); } } else { // Set a persistent tag to re-identify the correct hologram later container.set(persistentDataKey, PersistentDataType.LONG, position.getPosition()); - return (ArmorStand) n; + return getAsHologram(position, n, container); } } } if (createIfNoneExists) { // Spawn a new ArmorStand - ArmorStand hologram = (ArmorStand) loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND); + ArmorStand armorstand = (ArmorStand) loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND); + PersistentDataContainer container = armorstand.getPersistentDataContainer(); - // Set a persistent tag to re-identify the correct hologram later - PersistentDataContainer container = hologram.getPersistentDataContainer(); - container.set(persistentDataKey, PersistentDataType.LONG, position.getPosition()); - - hologram.setInvisible(true); - hologram.setInvulnerable(true); - hologram.setSilent(true); - hologram.setMarker(true); - hologram.setAI(false); - hologram.setGravity(false); - - return hologram; + return getAsHologram(position, armorstand, container); } else { return null; } } /** - * This removes the hologram at that given {@link Location}. + * This checks if a given {@link Entity} is an {@link ArmorStand} + * and whether it has the correct attributes to be considered a {@link Hologram}. + * + * @param n + * The {@link Entity} to check + * + * @return Whether this could be a hologram + */ + private boolean isHologram(@Nonnull Entity n) { + if (n instanceof ArmorStand) { + ArmorStand armorstand = (ArmorStand) n; + + // The absolute minimum requirements to count as a hologram + return !armorstand.isVisible() && armorstand.isSilent() && !armorstand.hasGravity(); + } else { + 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}. + * + * @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 = (ArmorStand) entity; + + 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; + } + } + + /** + * This updates the {@link Hologram}. + * You can use it to set the nametag or other properties. + *

+ * This method must be executed on the main {@link Server} {@link Thread}. + * + * @param loc + * The {@link Location} + * @param consumer + * The callback to run + */ + 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"); + + Runnable runnable = () -> { + Hologram hologram = getHologram(loc, true); + + if (hologram != null) { + consumer.accept(hologram); + } + }; + + if (Bukkit.isPrimaryThread()) { + runnable.run(); + } else { + SlimefunPlugin.runSync(runnable); + } + } + + /** + * This removes the {@link Hologram} at that given {@link Location}. *

* This method must be executed on the main {@link Server} {@link Thread}. * * @param loc * The {@link Location} * - * @return Whether the hologram could be removed, false if the hologram does not exist or was already removed + * @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()) { - ArmorStand hologram = getHologram(loc, false); + Hologram hologram = getHologram(loc, false); if (hologram != null) { hologram.remove(); @@ -197,52 +274,17 @@ public class HologramsService { } /** - * This updates the hologram. - * You can use it to set the nametag or other properties. - *

- * This method must be executed on the main {@link Server} {@link Thread}. + * This will update the label of the {@link Hologram}. * * @param loc - * The {@link Location} - * @param consumer - * The callback to run + * The {@link Location} of this {@link Hologram} + * @param label + * The label to set, can be null */ - public void updateHologram(@Nonnull Location loc, @Nonnull Consumer consumer) { + public void setHologramLabel(@Nonnull Location loc, @Nullable String label) { Validate.notNull(loc, "Location must not be null"); - Validate.notNull(consumer, "Callbacks must not be null"); - if (Bukkit.isPrimaryThread()) { - consumer.accept(getHologram(loc, true)); - } else { - SlimefunPlugin.runSync(() -> consumer.accept(getHologram(loc, true))); - } - } - - /** - * This checks if a given {@link Entity} is an {@link ArmorStand} - * and whether it has the correct attributes to be considered a hologram. - * - * @param n - * The {@link Entity} to check - * - * @return Whether this could be a hologram - */ - private boolean isHologram(@Nonnull Entity n) { - if (n instanceof ArmorStand) { - ArmorStand armorstand = (ArmorStand) n; - - // @formatter:off - return armorstand.isValid() - && armorstand.isInvisible() - && armorstand.isInvulnerable() - && armorstand.isSilent() - && armorstand.isMarker() - && !armorstand.hasAI() - && !armorstand.hasGravity(); - // @formatter:on - } else { - return false; - } + updateHologram(loc, hologram -> hologram.setLabel(label)); } } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/reactors/NetherStarReactor.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/reactors/NetherStarReactor.java index 5e7763058..dfa654470 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/reactors/NetherStarReactor.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/reactors/NetherStarReactor.java @@ -5,7 +5,6 @@ import javax.annotation.ParametersAreNonnullByDefault; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; @@ -43,10 +42,8 @@ public abstract class NetherStarReactor extends Reactor { @Override public void extraTick(@Nonnull Location l) { SlimefunPlugin.runSync(() -> { - ArmorStand hologram = getHologram(l.getBlock(), true); - - for (Entity entity : hologram.getNearbyEntities(5, 5, 5)) { - if (entity instanceof LivingEntity && entity.isValid()) { + for (Entity entity : l.getWorld().getNearbyEntities(l, 5, 5, 5, n -> n instanceof LivingEntity && n.isValid())) { + if (entity instanceof LivingEntity) { ((LivingEntity) entity).addPotionEffect(new PotionEffect(PotionEffectType.WITHER, 60, 1)); } }