From d1c26c69b8aeb9019f4c0de7ccbede5079d894f8 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 13:43:29 +0200 Subject: [PATCH 01/14] fix: item lines not showing to players upon joining (#204) --- .../holograms/api/holograms/Hologram.java | 14 +++++++------- .../holograms/api/holograms/HologramLine.java | 3 ++- .../holograms/api/listeners/PlayerListener.java | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java index 0a4dae7f..d19412f6 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java @@ -633,14 +633,10 @@ public boolean show(@NonNull Player player, int pageIndex) { if (page != null && page.size() > 0 && canShow(player) && isInDisplayRange(player)) { if (isVisible(player)) { hide(player); + return true; // Skip a tick before updating, should fix the previous issue with clients desyncing. } - if (Version.after(8)) { - showPageTo(player, page, pageIndex); - } else { - // We need to run the task later on older versions as, if we don't, it causes issues with some holograms *randomly* becoming invisible. - // I *think* this is from despawning and spawning the entities (with the same ID) in the same tick. - S.sync(() -> showPageTo(player, page, pageIndex), 0L); - } + + showPageTo(player, page, pageIndex); return true; } return false; @@ -648,6 +644,10 @@ public boolean show(@NonNull Player player, int pageIndex) { } private void showPageTo(@NonNull Player player, @NonNull HologramPage page, int pageIndex) { + if (page.getLines().stream().anyMatch(line -> !line.canShow(player))) { + return; + } + page.getLines().forEach(line -> line.show(player)); // Add player to viewers viewerPages.put(player.getUniqueId(), pageIndex); diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index b5fc122b..826c3df1 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -409,6 +409,7 @@ public void show(Player... players) { if (parent != null && parent.getParent().isHideState(player)) { continue; } + if (!isVisible(player) && canShow(player) && isInDisplayRange(player)) { switch (type) { case TEXT: @@ -568,7 +569,7 @@ public boolean hasFlag(@NonNull EnumFlag flag) { @Override public boolean canShow(@NonNull Player player) { - return super.canShow(player) && (parent == null || parent.getParent().canShow(player)); + return super.canShow(player) && (parent == null || parent.getParent().canShow(player)) && (type != HologramLineType.ICON || player.getTicksLived() > 40); // Could be considered hacky but it works? } } diff --git a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java index 6cb40bb2..718897a5 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java +++ b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java @@ -24,7 +24,6 @@ public PlayerListener(DecentHolograms decentHolograms) { @EventHandler(priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent e) { Player player = e.getPlayer(); - S.async(() -> decentHolograms.getHologramManager().updateVisibility(player)); decentHolograms.getPacketListener().hook(player); if (decentHolograms.isUpdateAvailable() && player.hasPermission("dh.admin")) { Lang.sendUpdateMessage(player); From fabd3b11c0e9485e0c2d3da1651743dd87e8a92e Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:11:01 +0200 Subject: [PATCH 02/14] feat: config option for ticks lived --- src/main/java/eu/decentsoftware/holograms/api/Settings.java | 2 ++ .../decentsoftware/holograms/api/holograms/HologramLine.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/Settings.java b/src/main/java/eu/decentsoftware/holograms/api/Settings.java index ae53bf68..95711194 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/Settings.java +++ b/src/main/java/eu/decentsoftware/holograms/api/Settings.java @@ -42,6 +42,8 @@ public class Settings { public static int DEFAULT_LRU_CACHE_SIZE = 500; @Key("allow-placeholders-inside-animations") public static boolean ALLOW_PLACEHOLDERS_INSIDE_ANIMATIONS = false; + @Key(value = "defaults.minimum-ticks-lived-item-line") + public static int DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE = 40; public static Map CUSTOM_REPLACEMENTS = ImmutableMap.builder() .put("[x]", "\u2588") diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index 826c3df1..96525e56 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -569,7 +569,9 @@ public boolean hasFlag(@NonNull EnumFlag flag) { @Override public boolean canShow(@NonNull Player player) { - return super.canShow(player) && (parent == null || parent.getParent().canShow(player)) && (type != HologramLineType.ICON || player.getTicksLived() > 40); // Could be considered hacky but it works? + return super.canShow(player) + && (parent == null || parent.getParent().canShow(player)) + && (type != HologramLineType.ICON || player.getTicksLived() > Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE); // Could be considered hacky but it works? } } From 36a3212bec403c69a81b2e86e9a013acb23da329 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:15:04 +0200 Subject: [PATCH 03/14] feat: tick update limiter --- .../holograms/api/Settings.java | 6 ++++- .../api/holograms/HologramManager.java | 26 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/Settings.java b/src/main/java/eu/decentsoftware/holograms/api/Settings.java index 95711194..bc245d31 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/Settings.java +++ b/src/main/java/eu/decentsoftware/holograms/api/Settings.java @@ -42,8 +42,12 @@ public class Settings { public static int DEFAULT_LRU_CACHE_SIZE = 500; @Key("allow-placeholders-inside-animations") public static boolean ALLOW_PLACEHOLDERS_INSIDE_ANIMATIONS = false; - @Key(value = "defaults.minimum-ticks-lived-item-line") + @Key(value = "defaults.minimum-ticks-lived-item-line", min = 0) public static int DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE = 40; + @Key(value = "defaults.limit-hologram-updates-per-tick") + public static boolean LIMIT_HOLOGRAM_UPDATES_PER_TICK = true; + @Key(value = "defaults.maximum-hologram-updates-per-tick", min = 1) + public static int MAXIMUM_HOLOGRAM_UPDATES_PER_TICK = 10; public static Map CUSTOM_REPLACEMENTS = ImmutableMap.builder() .put("[x]", "\u2588") diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java index 34301506..2e64be17 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java @@ -53,10 +53,18 @@ public HologramManager(DecentHolograms decentHolograms) { @Override public synchronized void tick() { + final Map updates = new LinkedHashMap<>(); + for (Hologram hologram : Hologram.getCachedHolograms()) { if (hologram.isEnabled()) { for (Player player : Bukkit.getOnlinePlayers()) { - updateVisibility(player, hologram); + if (Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK && updates.getOrDefault(player.getUniqueId(), 0) >= Settings.MAXIMUM_HOLOGRAM_UPDATES_PER_TICK) { + continue; + } + + if (updateVisibility(player, hologram) && Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK) { + updates.put(player.getUniqueId(), updates.getOrDefault(player.getUniqueId(), 0) + 1); + } } } } @@ -68,9 +76,16 @@ public void updateVisibility(@NonNull Player player) { } } - public void updateVisibility(@NonNull Player player, @NonNull Hologram hologram) { + /** + * Update the visibility of the hologram for the given player. + * + * @param player - The player to update the visibility for. + * @param hologram - The hologram to update the visibility for. + * @return True if the hologram was shown, false otherwise. + */ + public boolean updateVisibility(@NonNull Player player, @NonNull Hologram hologram) { if (hologram.isDisabled()) { - return; + return false; } // Determine the player's display state of this hologram. @@ -78,14 +93,17 @@ public void updateVisibility(@NonNull Player player, @NonNull Hologram hologram) if (hologram.isVisible(player)) { hologram.hide(player); } - return; + return false; } if (!hologram.isVisible(player) && hologram.canShow(player) && hologram.isInDisplayRange(player)) { hologram.show(player, hologram.getPlayerPage(player)); + return true; } else if (hologram.isVisible(player) && !(hologram.canShow(player) && hologram.isInDisplayRange(player))) { hologram.hide(player); } + + return false; } /** From 85a2c759ca59a1b3bc088f7b4d95d9b28f6cb370 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:23:31 +0200 Subject: [PATCH 04/14] fix: potential permission-based lines breaking --- .../decentsoftware/holograms/api/holograms/Hologram.java | 2 +- .../holograms/api/holograms/HologramLine.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java index d19412f6..883a5c38 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java @@ -644,7 +644,7 @@ public boolean show(@NonNull Player player, int pageIndex) { } private void showPageTo(@NonNull Player player, @NonNull HologramPage page, int pageIndex) { - if (page.getLines().stream().anyMatch(line -> !line.canShow(player))) { + if (page.getLines().stream().anyMatch(line -> line.shouldAwaitDisplay(player))) { return; } diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index 96525e56..ce3974e4 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -569,9 +569,11 @@ public boolean hasFlag(@NonNull EnumFlag flag) { @Override public boolean canShow(@NonNull Player player) { - return super.canShow(player) - && (parent == null || parent.getParent().canShow(player)) - && (type != HologramLineType.ICON || player.getTicksLived() > Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE); // Could be considered hacky but it works? + return super.canShow(player) && (parent == null || parent.getParent().canShow(player)); } + // Could be considered hacky but it works? + public boolean shouldAwaitDisplay(Player player) { + return (type != HologramLineType.ICON || player.getTicksLived() > Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE); + } } From 80d51b31a294f7bb9665d22eb595eb07fbac6224 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:27:46 +0200 Subject: [PATCH 05/14] fix: oops, make the method do what it should --- .../eu/decentsoftware/holograms/api/holograms/HologramLine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index ce3974e4..0770194b 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -574,6 +574,6 @@ public boolean canShow(@NonNull Player player) { // Could be considered hacky but it works? public boolean shouldAwaitDisplay(Player player) { - return (type != HologramLineType.ICON || player.getTicksLived() > Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE); + return (type == HologramLineType.ICON || type == HologramLineType.HEAD) && player.getTicksLived() < Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE; } } From 6f90f6fcfcc135f67801c8172d053e1ea5b4fc17 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:43:32 +0200 Subject: [PATCH 06/14] feat: store session login epochs --- .../holograms/api/DecentHolograms.java | 3 ++- .../decentsoftware/holograms/api/Settings.java | 4 ++-- .../holograms/api/holograms/HologramLine.java | 3 ++- .../holograms/api/holograms/HologramManager.java | 3 ++- .../holograms/api/listeners/PlayerListener.java | 16 ++++++++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java b/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java index 96b64521..292e4763 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java +++ b/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java @@ -48,6 +48,7 @@ public final class DecentHolograms { private FeatureManager featureManager; private AnimationManager animationManager; private PacketListener packetListener; + private PlayerListener playerListener; private Ticker ticker; private boolean updateAvailable; @@ -86,7 +87,7 @@ void enable() { this.packetListener = new PacketListener(this); PluginManager pm = Bukkit.getPluginManager(); - pm.registerEvents(new PlayerListener(this), this.plugin); + pm.registerEvents((playerListener = new PlayerListener(this)), this.plugin); pm.registerEvents(new WorldListener(this), this.plugin); // Setup metrics diff --git a/src/main/java/eu/decentsoftware/holograms/api/Settings.java b/src/main/java/eu/decentsoftware/holograms/api/Settings.java index bc245d31..ac1cf915 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/Settings.java +++ b/src/main/java/eu/decentsoftware/holograms/api/Settings.java @@ -42,8 +42,8 @@ public class Settings { public static int DEFAULT_LRU_CACHE_SIZE = 500; @Key("allow-placeholders-inside-animations") public static boolean ALLOW_PLACEHOLDERS_INSIDE_ANIMATIONS = false; - @Key(value = "defaults.minimum-ticks-lived-item-line", min = 0) - public static int DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE = 40; + @Key(value = "defaults.minimum-session-ticks-item-line", min = 0) + public static int DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE = 40; @Key(value = "defaults.limit-hologram-updates-per-tick") public static boolean LIMIT_HOLOGRAM_UPDATES_PER_TICK = true; @Key(value = "defaults.maximum-hologram-updates-per-tick", min = 1) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index 0770194b..75f9c2d2 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -574,6 +574,7 @@ public boolean canShow(@NonNull Player player) { // Could be considered hacky but it works? public boolean shouldAwaitDisplay(Player player) { - return (type == HologramLineType.ICON || type == HologramLineType.HEAD) && player.getTicksLived() < Settings.DEFAULT_MINIMUM_TICKS_LIVED_ITEM_LINE; + return (type == HologramLineType.ICON || type == HologramLineType.HEAD || type == HologramLineType.SMALLHEAD) + && DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) < Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE; } } diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java index 2e64be17..09a027e3 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java @@ -58,7 +58,8 @@ public synchronized void tick() { for (Hologram hologram : Hologram.getCachedHolograms()) { if (hologram.isEnabled()) { for (Player player : Bukkit.getOnlinePlayers()) { - if (Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK && updates.getOrDefault(player.getUniqueId(), 0) >= Settings.MAXIMUM_HOLOGRAM_UPDATES_PER_TICK) { + if (Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK + && updates.getOrDefault(player.getUniqueId(), 0) >= Settings.MAXIMUM_HOLOGRAM_UPDATES_PER_TICK) { continue; } diff --git a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java index 718897a5..de87f228 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java +++ b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java @@ -12,10 +12,15 @@ import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + @SuppressWarnings("unused") public class PlayerListener implements Listener { private final DecentHolograms decentHolograms; + private final Map playerLoginTime = new ConcurrentHashMap<>(); // Hologram lines can be updated asynchronously public PlayerListener(DecentHolograms decentHolograms) { this.decentHolograms = decentHolograms; @@ -28,6 +33,8 @@ public void onJoin(PlayerJoinEvent e) { if (decentHolograms.isUpdateAvailable() && player.hasPermission("dh.admin")) { Lang.sendUpdateMessage(player); } + + playerLoginTime.put(player.getUniqueId(), System.currentTimeMillis()); } @EventHandler @@ -58,4 +65,13 @@ public void onTeleport(PlayerTeleportEvent e) { S.async(() -> decentHolograms.getHologramManager().hideAll(player)); } + public int getTicksSinceLogin(Player player) { + Long epoch = playerLoginTime.get(player.getUniqueId()); + + if (epoch == null) { + return 0; + } + + return (int) ((System.currentTimeMillis() - epoch)) / 50; + } } From b7856b6c81a36d7e51d768cf2caa0da2c9385085 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:43:41 +0200 Subject: [PATCH 07/14] memory cleanup --- .../decentsoftware/holograms/api/listeners/PlayerListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java index de87f228..f514f1c6 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java +++ b/src/main/java/eu/decentsoftware/holograms/api/listeners/PlayerListener.java @@ -42,6 +42,7 @@ public void onQuit(PlayerQuitEvent e) { Player player = e.getPlayer(); S.async(() -> decentHolograms.getHologramManager().onQuit(player)); decentHolograms.getPacketListener().unhook(player); + playerLoginTime.remove(player.getUniqueId()); } @EventHandler From 3e8949fa2bbd59900e2e3a6d31634d63c65d1178 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 14:53:23 +0200 Subject: [PATCH 08/14] revert: limiting hologram updates per tick --- .../holograms/api/Settings.java | 4 ---- .../api/holograms/HologramManager.java | 20 ++++--------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/Settings.java b/src/main/java/eu/decentsoftware/holograms/api/Settings.java index ac1cf915..0d4bfdaa 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/Settings.java +++ b/src/main/java/eu/decentsoftware/holograms/api/Settings.java @@ -44,10 +44,6 @@ public class Settings { public static boolean ALLOW_PLACEHOLDERS_INSIDE_ANIMATIONS = false; @Key(value = "defaults.minimum-session-ticks-item-line", min = 0) public static int DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE = 40; - @Key(value = "defaults.limit-hologram-updates-per-tick") - public static boolean LIMIT_HOLOGRAM_UPDATES_PER_TICK = true; - @Key(value = "defaults.maximum-hologram-updates-per-tick", min = 1) - public static int MAXIMUM_HOLOGRAM_UPDATES_PER_TICK = 10; public static Map CUSTOM_REPLACEMENTS = ImmutableMap.builder() .put("[x]", "\u2588") diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java index 09a027e3..48ac3e49 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java @@ -53,19 +53,10 @@ public HologramManager(DecentHolograms decentHolograms) { @Override public synchronized void tick() { - final Map updates = new LinkedHashMap<>(); - for (Hologram hologram : Hologram.getCachedHolograms()) { if (hologram.isEnabled()) { for (Player player : Bukkit.getOnlinePlayers()) { - if (Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK - && updates.getOrDefault(player.getUniqueId(), 0) >= Settings.MAXIMUM_HOLOGRAM_UPDATES_PER_TICK) { - continue; - } - - if (updateVisibility(player, hologram) && Settings.LIMIT_HOLOGRAM_UPDATES_PER_TICK) { - updates.put(player.getUniqueId(), updates.getOrDefault(player.getUniqueId(), 0) + 1); - } + updateVisibility(player, hologram); } } } @@ -84,9 +75,9 @@ public void updateVisibility(@NonNull Player player) { * @param hologram - The hologram to update the visibility for. * @return True if the hologram was shown, false otherwise. */ - public boolean updateVisibility(@NonNull Player player, @NonNull Hologram hologram) { + public void updateVisibility(@NonNull Player player, @NonNull Hologram hologram) { if (hologram.isDisabled()) { - return false; + return; } // Determine the player's display state of this hologram. @@ -94,17 +85,14 @@ public boolean updateVisibility(@NonNull Player player, @NonNull Hologram hologr if (hologram.isVisible(player)) { hologram.hide(player); } - return false; + return; } if (!hologram.isVisible(player) && hologram.canShow(player) && hologram.isInDisplayRange(player)) { hologram.show(player, hologram.getPlayerPage(player)); - return true; } else if (hologram.isVisible(player) && !(hologram.canShow(player) && hologram.isInDisplayRange(player))) { hologram.hide(player); } - - return false; } /** From 2bf7b4134464f5d21793ee0ae6cb1251be3a445d Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 15:21:41 +0200 Subject: [PATCH 09/14] feat: use queue for line updating, add item display range --- .../holograms/api/DecentHolograms.java | 3 + .../holograms/api/Settings.java | 2 + .../holograms/api/holograms/Hologram.java | 20 ++++- .../holograms/api/holograms/HologramLine.java | 45 ++++++---- .../holograms/HologramLineDisplayHandler.java | 84 +++++++++++++++++++ .../api/holograms/HologramManager.java | 1 + .../objects/UpdatingHologramObject.java | 1 + 7 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java diff --git a/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java b/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java index 292e4763..809142b1 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java +++ b/src/main/java/eu/decentsoftware/holograms/api/DecentHolograms.java @@ -4,6 +4,7 @@ import eu.decentsoftware.holograms.api.commands.CommandManager; import eu.decentsoftware.holograms.api.features.FeatureManager; import eu.decentsoftware.holograms.api.holograms.Hologram; +import eu.decentsoftware.holograms.api.holograms.HologramLineDisplayHandler; import eu.decentsoftware.holograms.api.holograms.HologramManager; import eu.decentsoftware.holograms.api.nms.NMS; import eu.decentsoftware.holograms.api.nms.PacketListener; @@ -44,6 +45,7 @@ public final class DecentHolograms { private final JavaPlugin plugin; private HologramManager hologramManager; + private HologramLineDisplayHandler lineDisplayHandler; private CommandManager commandManager; private FeatureManager featureManager; private AnimationManager animationManager; @@ -81,6 +83,7 @@ void enable() { this.ticker = new Ticker(); this.hologramManager = new HologramManager(this); + this.lineDisplayHandler = new HologramLineDisplayHandler(); this.commandManager = new CommandManager(); this.featureManager = new FeatureManager(); this.animationManager = new AnimationManager(this); diff --git a/src/main/java/eu/decentsoftware/holograms/api/Settings.java b/src/main/java/eu/decentsoftware/holograms/api/Settings.java index 0d4bfdaa..67a24310 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/Settings.java +++ b/src/main/java/eu/decentsoftware/holograms/api/Settings.java @@ -34,6 +34,8 @@ public class Settings { public static double DEFAULT_HEIGHT_SMALLHEAD = 0.6; @Key(value = "defaults.display-range", min = 1, max = 48) public static int DEFAULT_DISPLAY_RANGE = 48; + @Key(value = "defaults.item-display-range", min = 1, max = 48) + public static int DEFAULT_ITEM_DISPLAY_RANGE = 16; @Key(value = "defaults.update-range", min = 1, max = 48) public static int DEFAULT_UPDATE_RANGE = 48; @Key(value = "defaults.update-interval", min = 1, max = 1200) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java index 883a5c38..f5256a02 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java @@ -644,10 +644,6 @@ public boolean show(@NonNull Player player, int pageIndex) { } private void showPageTo(@NonNull Player player, @NonNull HologramPage page, int pageIndex) { - if (page.getLines().stream().anyMatch(line -> line.shouldAwaitDisplay(player))) { - return; - } - page.getLines().forEach(line -> line.show(player)); // Add player to viewers viewerPages.put(player.getUniqueId(), pageIndex); @@ -814,6 +810,22 @@ public boolean isInDisplayRange(@NonNull Player player) { return false; } + public boolean isInItemDisplayRange(@NonNull Player player) { + /* + * Some forks (e.g. Pufferfish) throw an exception, when we try to get + * the world of a location, which is not loaded. We catch this exception + * and return false, because the player is not in range. + */ + try { + if (player.getWorld().equals(location.getWorld())) { + return player.getLocation().distanceSquared(location) <= itemDisplayRange * itemDisplayRange; + } + } catch (Exception ignored) { + // Ignored + } + return false; + } + /** * Check whether the given player is in update range of this hologram object. * diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index 75f9c2d2..106368fc 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -410,6 +410,8 @@ public void show(Player... players) { continue; } + DECENT_HOLOGRAMS.getLineDisplayHandler().removeFromQueue(this, player); + if (!isVisible(player) && canShow(player) && isInDisplayRange(player)) { switch (type) { case TEXT: @@ -419,14 +421,11 @@ public void show(Player... players) { case HEAD: case SMALLHEAD: nms.showFakeEntityArmorStand(player, getLocation(), entityIds[0], true, HologramLineType.HEAD != type, false); - ItemStack itemStack = containsPlaceholders ? HologramItem.parseItemStack(item.getContent(), player) : item.parse(); - nms.helmetFakeEntity(player, itemStack, entityIds[0]); + DECENT_HOLOGRAMS.getLineDisplayHandler().queue(this, player); break; case ICON: nms.showFakeEntityArmorStand(player, getLocation(), entityIds[0], true, true, false); - ItemStack itemStack1 = containsPlaceholders ? HologramItem.parseItemStack(item.getContent(), player) : item.parse(); - nms.showFakeEntityItem(player, getLocation(), itemStack1, entityIds[1]); - nms.attachFakeEntity(player, entityIds[0], entityIds[1]); + DECENT_HOLOGRAMS.getLineDisplayHandler().queue(this, player); break; case ENTITY: EntityType entityType = new HologramEntity(PAPI.setPlaceholders(player, getEntity().getContent())).getType(); @@ -448,6 +447,26 @@ public void show(Player... players) { } } + protected void showItem(Player player) { + if (!viewers.contains(player.getUniqueId())) { + return; + } + + NMS nms = NMS.getInstance(); + + switch (type) { + case HEAD: + case SMALLHEAD: + ItemStack itemStack = containsPlaceholders ? HologramItem.parseItemStack(item.getContent(), player) : item.parse(); + nms.helmetFakeEntity(player, itemStack, entityIds[0]); + break; + case ICON: + ItemStack itemStack1 = containsPlaceholders ? HologramItem.parseItemStack(item.getContent(), player) : item.parse(); + nms.showFakeEntityItem(player, getLocation(), itemStack1, entityIds[1]); + nms.attachFakeEntity(player, entityIds[0], entityIds[1]); + } + } + /** * Update this line for given players. * @@ -520,6 +539,7 @@ public void updateAnimations(Player... players) { public void hide(Player... players) { List playerList = getPlayers(true, players); for (Player player : playerList) { + DECENT_HOLOGRAMS.getLineDisplayHandler().removeFromQueue(this, player); NMS.getInstance().hideFakeEntities(player, entityIds[0], entityIds[1]); viewers.remove(player.getUniqueId()); } @@ -529,6 +549,10 @@ public boolean isInDisplayRange(@NonNull Player player) { return parent == null || parent.getParent().isInDisplayRange(player); } + public boolean isInItemDisplayRange(@NonNull Player player) { + return parent == null || parent.getParent().isInItemDisplayRange(player); + } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isInUpdateRange(@NonNull Player player) { return parent == null || parent.getParent().isInUpdateRange(player); @@ -566,15 +590,4 @@ public void setOffsetZ(double offsetZ) { public boolean hasFlag(@NonNull EnumFlag flag) { return super.hasFlag(flag) || (parent != null && parent.getParent().hasFlag(flag)); } - - @Override - public boolean canShow(@NonNull Player player) { - return super.canShow(player) && (parent == null || parent.getParent().canShow(player)); - } - - // Could be considered hacky but it works? - public boolean shouldAwaitDisplay(Player player) { - return (type == HologramLineType.ICON || type == HologramLineType.HEAD || type == HologramLineType.SMALLHEAD) - && DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) < Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE; - } } diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java new file mode 100644 index 00000000..5c61aa10 --- /dev/null +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -0,0 +1,84 @@ +package eu.decentsoftware.holograms.api.holograms; + +import eu.decentsoftware.holograms.api.DecentHologramsAPI; +import eu.decentsoftware.holograms.api.Settings; +import eu.decentsoftware.holograms.api.utils.tick.Ticked; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.LinkedHashSet; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class HologramLineDisplayHandler extends Ticked { + + private final Queue queue = new ConcurrentLinkedQueue<>(); + + public HologramLineDisplayHandler() { + super(1L); + this.register(); + } + + public void queue(HologramLine line, Player player) { + if (queue.stream().anyMatch(entry -> entry.getLine() == line && player.getUniqueId().equals(entry.getPlayerId()))) { + return; + } + + queue.add(new LineDisplayEntry(line, player.getUniqueId())); + } + + public void removeFromQueue(HologramLine line, Player player) { + queue.removeIf(entry -> entry.getLine() == line && player.getUniqueId().equals(entry.getPlayerId())); + } + + @Override + public void tick() { + final Set playersUpdated = new LinkedHashSet<>(); // We only want to send the player one item per tick + + while (!queue.isEmpty()) { + LineDisplayEntry poll = queue.poll(); + + if (poll == null) { + continue; + } + + Player player = Bukkit.getPlayer(poll.getPlayerId()); + + if (player == null) { + continue; + } + + if (playersUpdated.contains(player.getUniqueId())) { + queue.add(poll); + continue; + } + + if (canDisplay(player, poll.getLine())) { + poll.getLine().showItem(player); + playersUpdated.add(player.getUniqueId()); + } else { + queue.add(poll); + } + } + } + + private boolean canDisplay(Player player, HologramLine line) { + return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) < Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE + && line.isInItemDisplayRange(player); + } + + @Getter + private static class LineDisplayEntry { + + private final HologramLine line; + private final UUID playerId; + + public LineDisplayEntry(HologramLine line, UUID playerId) { + this.line = line; + this.playerId = playerId; + } + } +} diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java index 48ac3e49..490e02a8 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramManager.java @@ -8,6 +8,7 @@ import eu.decentsoftware.holograms.api.utils.file.FileUtils; import eu.decentsoftware.holograms.api.utils.scheduler.S; import eu.decentsoftware.holograms.api.utils.tick.Ticked; +import lombok.Getter; import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.Location; diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java index f89dc9ec..00c132da 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java @@ -15,6 +15,7 @@ public abstract class UpdatingHologramObject extends HologramObject { */ protected int displayRange = Settings.DEFAULT_DISPLAY_RANGE; + protected int itemDisplayRange = Settings.DEFAULT_ITEM_DISPLAY_RANGE; protected int updateRange = Settings.DEFAULT_UPDATE_RANGE; protected volatile int updateInterval = Settings.DEFAULT_UPDATE_INTERVAL; From db66bdfce4c215f8d77cf48036250616bebde101 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 15:34:40 +0200 Subject: [PATCH 10/14] fix queue issue --- .../api/holograms/HologramLineDisplayHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java index 5c61aa10..e1f71da1 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -37,6 +37,7 @@ public void removeFromQueue(HologramLine line, Player player) { @Override public void tick() { final Set playersUpdated = new LinkedHashSet<>(); // We only want to send the player one item per tick + final Set reQueue = new LinkedHashSet<>(); while (!queue.isEmpty()) { LineDisplayEntry poll = queue.poll(); @@ -52,7 +53,7 @@ public void tick() { } if (playersUpdated.contains(player.getUniqueId())) { - queue.add(poll); + reQueue.add(poll); continue; } @@ -60,9 +61,11 @@ public void tick() { poll.getLine().showItem(player); playersUpdated.add(player.getUniqueId()); } else { - queue.add(poll); + reQueue.add(poll); } } + + queue.addAll(reQueue); } private boolean canDisplay(Player player, HologramLine line) { From d2ad400530cae72b2fed77fe9e77344134a2b11c Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 15:41:33 +0200 Subject: [PATCH 11/14] fully functional --- .../holograms/api/holograms/HologramLineDisplayHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java index e1f71da1..9e00e544 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -69,7 +69,7 @@ public void tick() { } private boolean canDisplay(Player player, HologramLine line) { - return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) < Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE + return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) >= Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE && line.isInItemDisplayRange(player); } From edb2adc3112cd6a5e861fecc3a29daa7af99f3fe Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 17 Apr 2024 16:01:04 +0200 Subject: [PATCH 12/14] optimize --- .../api/holograms/HologramLineDisplayHandler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java index 9e00e544..36388c58 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -57,8 +57,14 @@ public void tick() { continue; } - if (canDisplay(player, poll.getLine())) { - poll.getLine().showItem(player); + HologramLine line = poll.getLine(); + + if (!line.isVisible(player)) { + continue; + } + + if (canDisplay(player, line)) { + line.showItem(player); playersUpdated.add(player.getUniqueId()); } else { reQueue.add(poll); From 283a3c39255630bb4aafad02f3b8da4f3a597037 Mon Sep 17 00:00:00 2001 From: bridge Date: Sat, 20 Apr 2024 21:49:49 +0200 Subject: [PATCH 13/14] get rid of item display range --- .../holograms/api/holograms/Hologram.java | 16 ---------------- .../holograms/api/holograms/HologramLine.java | 4 ---- .../holograms/HologramLineDisplayHandler.java | 7 ++++--- .../objects/UpdatingHologramObject.java | 1 - 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java index f5256a02..0bb547a1 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/Hologram.java @@ -810,22 +810,6 @@ public boolean isInDisplayRange(@NonNull Player player) { return false; } - public boolean isInItemDisplayRange(@NonNull Player player) { - /* - * Some forks (e.g. Pufferfish) throw an exception, when we try to get - * the world of a location, which is not loaded. We catch this exception - * and return false, because the player is not in range. - */ - try { - if (player.getWorld().equals(location.getWorld())) { - return player.getLocation().distanceSquared(location) <= itemDisplayRange * itemDisplayRange; - } - } catch (Exception ignored) { - // Ignored - } - return false; - } - /** * Check whether the given player is in update range of this hologram object. * diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java index 106368fc..7cce2e9f 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLine.java @@ -549,10 +549,6 @@ public boolean isInDisplayRange(@NonNull Player player) { return parent == null || parent.getParent().isInDisplayRange(player); } - public boolean isInItemDisplayRange(@NonNull Player player) { - return parent == null || parent.getParent().isInItemDisplayRange(player); - } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isInUpdateRange(@NonNull Player player) { return parent == null || parent.getParent().isInUpdateRange(player); diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java index 36388c58..f9b3ab3f 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -72,11 +72,12 @@ public void tick() { } queue.addAll(reQueue); + playersUpdated.clear(); + reQueue.clear(); } - private boolean canDisplay(Player player, HologramLine line) { - return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) >= Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE - && line.isInItemDisplayRange(player); + private boolean canDisplay(Player player) { + return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) >= Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE; } @Getter diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java index 00c132da..f89dc9ec 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/objects/UpdatingHologramObject.java @@ -15,7 +15,6 @@ public abstract class UpdatingHologramObject extends HologramObject { */ protected int displayRange = Settings.DEFAULT_DISPLAY_RANGE; - protected int itemDisplayRange = Settings.DEFAULT_ITEM_DISPLAY_RANGE; protected int updateRange = Settings.DEFAULT_UPDATE_RANGE; protected volatile int updateInterval = Settings.DEFAULT_UPDATE_INTERVAL; From fac9810e33e2c1d1f3dff18e801fd2cf5874af20 Mon Sep 17 00:00:00 2001 From: bridge Date: Sat, 20 Apr 2024 21:51:19 +0200 Subject: [PATCH 14/14] add cleanup --- .../api/holograms/HologramLineDisplayHandler.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java index f9b3ab3f..5b516653 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java +++ b/src/main/java/eu/decentsoftware/holograms/api/holograms/HologramLineDisplayHandler.java @@ -12,9 +12,13 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; public class HologramLineDisplayHandler extends Ticked { + private static final long TTL = TimeUnit.SECONDS.toMillis(30); + private static final int MAX_QUEUE_SIZE = 10_000; + private final Queue queue = new ConcurrentLinkedQueue<>(); public HologramLineDisplayHandler() { @@ -46,6 +50,10 @@ public void tick() { continue; } + if (System.currentTimeMillis() - poll.getTimestamp() > TTL) { + continue; + } + Player player = Bukkit.getPlayer(poll.getPlayerId()); if (player == null) { @@ -63,7 +71,7 @@ public void tick() { continue; } - if (canDisplay(player, line)) { + if (canDisplay(player)) { line.showItem(player); playersUpdated.add(player.getUniqueId()); } else { @@ -74,6 +82,10 @@ public void tick() { queue.addAll(reQueue); playersUpdated.clear(); reQueue.clear(); + + if (queue.size() >= MAX_QUEUE_SIZE) { + queue.clear(); + } } private boolean canDisplay(Player player) { @@ -85,6 +97,7 @@ private static class LineDisplayEntry { private final HologramLine line; private final UUID playerId; + private final long timestamp = System.currentTimeMillis(); public LineDisplayEntry(HologramLine line, UUID playerId) { this.line = line;