From 7fa988cc75a064e48f360d35cbd164012b8948a9 Mon Sep 17 00:00:00 2001 From: ssquadteam Date: Fri, 16 Jan 2026 22:07:20 +0600 Subject: [PATCH 1/4] feat: Introduce item holograms and refactor existing hologram types to a common interface. --- .../com/tcoded/hologramlib/HologramLib.java | 1 + .../tcoded/hologramlib/hologram/Hologram.java | 38 ++ .../hologramlib/hologram/ItemHologram.java | 347 ++++++++++++++++++ .../hologram/ItemHologramLine.java | 43 +++ .../hologramlib/hologram/TextHologram.java | 2 +- .../hologram/meta/ItemDisplayMeta.java | 22 ++ .../hologramlib/manager/HologramManager.java | 81 +++- .../hologramlib/manager/PlayerManager.java | 8 +- .../tracker/HologramPlayerTracker.java | 12 +- .../tracker/PlayerHologramTracker.java | 14 +- .../types/HologramPlayerAction.java | 4 +- .../hologramlib/types/LocationUpdateHook.java | 4 +- .../types/desync/DesyncAction.java | 4 +- .../types/desync/ManualDesyncAction.java | 4 +- .../types/desync/MoveDesyncAction.java | 4 +- .../types/desync/SpecificDesyncAction.java | 4 +- .../utils/HologramLookupCache.java | 20 +- .../nms/v1_20_4/NmsHologramManager.java | 7 +- .../nms/v1_20_4/NmsItemHologramLine.java | 229 ++++++++++++ .../nms/v1_21_4/NmsHologramManager.java | 6 + .../nms/v1_21_4/NmsItemHologramLine.java | 236 ++++++++++++ 21 files changed, 1043 insertions(+), 47 deletions(-) create mode 100644 common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java create mode 100644 common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java create mode 100644 common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologramLine.java create mode 100644 common/src/main/java/com/tcoded/hologramlib/hologram/meta/ItemDisplayMeta.java create mode 100644 nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsItemHologramLine.java create mode 100644 nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsItemHologramLine.java diff --git a/common/src/main/java/com/tcoded/hologramlib/HologramLib.java b/common/src/main/java/com/tcoded/hologramlib/HologramLib.java index d6c65ee..b7440b9 100644 --- a/common/src/main/java/com/tcoded/hologramlib/HologramLib.java +++ b/common/src/main/java/com/tcoded/hologramlib/HologramLib.java @@ -82,6 +82,7 @@ public HologramLib(Plugin plugin) { } catch (Exception e) { // noinspection CallToPrintStackTrace e.printStackTrace(); + throw new IllegalStateException("Failed to load NMS HologramManager for Minecraft version " + mcVersion + ". This version may not be supported.", e); } this.hologramManager.withCache(hologramLookupCache); diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java new file mode 100644 index 0000000..e045ca3 --- /dev/null +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java @@ -0,0 +1,38 @@ +package com.tcoded.hologramlib.hologram; + +import com.tcoded.hologramlib.tracker.HologramPlayerTracker; +import com.tcoded.hologramlib.types.LocationUpdateHook; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.List; + +public interface Hologram { + + InternalIdType getInternalId(); + + Location getLocation(); + + void setLocation(Location location); + + void hookLocationUpdate(LocationUpdateHook hook); + + float getTrackingDistance(); + + void setTrackingDistance(float trackingDistance); + + HologramPlayerTracker getTracker(); + + boolean isVisible(); + + void show(); + + void show(List players); + + void hide(); + + void hide(List players); + + void updateMeta(); + +} diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java new file mode 100644 index 0000000..4a3c460 --- /dev/null +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java @@ -0,0 +1,347 @@ +package com.tcoded.hologramlib.hologram; + +import com.google.common.collect.ImmutableList; +import com.tcoded.hologramlib.tracker.HologramPlayerTracker; +import com.tcoded.hologramlib.types.LocationUpdateHook; +import com.tcoded.hologramlib.utils.SyncCatcher; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; + +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; + +public class ItemHologram implements Hologram { + + private final InternalIdType internalId; + private final HologramPlayerTracker tracker; + + private ImmutableList lines; + private ReentrantLock linesLock; + + private boolean visible; + private ReentrantLock visibleLock; + + private float trackingDistance; + + private Location location; + private boolean locationSynced; + private Location lastSyncedLocation; + private LocationUpdateHook locationUpdateHook; + + public ItemHologram(InternalIdType internalId) { + this.internalId = internalId; + this.tracker = new HologramPlayerTracker(this); + + this.lines = ImmutableList.of(); + this.linesLock = new ReentrantLock(); + + this.visible = false; + this.visibleLock = new ReentrantLock(); + + this.trackingDistance = 40.0f; + + this.location = null; + this.locationSynced = false; + this.lastSyncedLocation = null; + this.locationUpdateHook = null; + } + + @Override + public void show() { + SyncCatcher.ensureAsync(); + + this.visibleLock.lock(); + try { + if (this.isVisible()) { + throw new IllegalStateException("Hologram already visible"); + } + + if (this.location == null) { + throw new IllegalStateException("Hologram location is null"); + } + + if (!this.locationSynced) { + this.syncLocation(); + } + + this.setVisible(true); + + List viewers = this.tracker.getAllViewingPlayers(); + this.show(viewers); + } finally { + this.visibleLock.unlock(); + } + } + + @ApiStatus.Internal + public void show(List players) { + SyncCatcher.ensureAsync(); + + this.sendSpawnPackets(players); + this.sendMetaPackets(players); + } + + @Override + public void hide() { + SyncCatcher.ensureAsync(); + + this.visibleLock.lock(); + try { + if (!this.isVisible()) { + throw new IllegalStateException("Hologram already hidden"); + } + + this.setVisible(false); + + List viewers = this.tracker.getAllViewingPlayers(); + this.hide(viewers); + } finally { + this.visibleLock.unlock(); + } + } + + @ApiStatus.Internal + public void hide(List players) { + this.sendKillPackets(players); + } + + @Override + public void updateMeta() { + SyncCatcher.ensureAsync(); + + List viewers = this.tracker.getAllViewingPlayers(); + sendMetaPackets(viewers); + } + + @Override + public InternalIdType getInternalId() { + return this.internalId; + } + + @Override + public Location getLocation() { + return location.clone(); + } + + @Override + public void setLocation(Location location) { + boolean holoVisible; + + this.visibleLock.lock(); + try { + holoVisible = this.isVisible(); + } finally { + this.visibleLock.unlock(); + } + + if (holoVisible) SyncCatcher.ensureAsync(); + + this.location = location.clone(); + + if (holoVisible && this.syncLocation()) { + this.sendTeleportPackets(this.tracker.getAllViewingPlayers()); + } + } + + @Override + public void hookLocationUpdate(LocationUpdateHook hook) { + this.locationUpdateHook = hook; + } + + @Override + public float getTrackingDistance() { + return trackingDistance; + } + + @Override + public void setTrackingDistance(float trackingDistance) { + this.trackingDistance = trackingDistance; + } + + @Override + public HologramPlayerTracker getTracker() { + return this.tracker; + } + + @Override + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean isVisible() { + this.visibleLock.lock(); + try { + return this.visible; + } finally { + this.visibleLock.unlock(); + } + } + + private void setVisible(boolean state) { + this.visibleLock.lock(); + try { + this.visible = state; + } finally { + this.visibleLock.unlock(); + } + } + + public List getLines() { + try { + this.linesLock.lock(); + return this.lines; + } finally { + this.linesLock.unlock(); + } + } + + public ItemHologramLine getLine(int index) { + try { + this.linesLock.lock(); + + if (index < 0 || index >= this.lines.size()) { + throw new IndexOutOfBoundsException("Index out of bounds"); + } + + return this.lines.get(index); + } finally { + this.linesLock.unlock(); + } + } + + public void addLine(ItemHologramLine line) { + boolean holoVisible; + + this.visibleLock.lock(); + try { + holoVisible = this.isVisible(); + } finally { + this.visibleLock.unlock(); + } + + if (holoVisible) SyncCatcher.ensureAsync(); + + ImmutableList.Builder linesBuilder = ImmutableList.builder(); + linesBuilder.addAll(this.lines); + linesBuilder.add(line); + + try { + this.linesLock.lock(); + + this.lines = linesBuilder.build(); + + if (holoVisible) { + this.syncLineLocations(); + this.sendTeleportPackets(this.tracker.getAllViewingPlayers()); + } + } finally { + this.linesLock.unlock(); + } + } + + public void removeLine(int index) { + boolean holoVisible; + + this.visibleLock.lock(); + try { + holoVisible = this.isVisible(); + } finally { + this.visibleLock.unlock(); + } + + if (holoVisible) SyncCatcher.ensureAsync(); + + try { + this.linesLock.lock(); + + if (index < 0 || index >= this.lines.size()) { + throw new IndexOutOfBoundsException("Index out of bounds"); + } + + ImmutableList.Builder linesBuilder = ImmutableList.builder(); + for (int i = 0; i < this.lines.size(); i++) { + if (i != index) { + linesBuilder.add(this.lines.get(i)); + } + } + + this.lines = linesBuilder.build(); + + if (holoVisible) { + this.sendTeleportPackets(this.tracker.getAllViewingPlayers()); + this.syncLineLocations(); + } + } finally { + this.linesLock.unlock(); + } + + if (holoVisible) this.sendTeleportPackets(this.tracker.getAllViewingPlayers()); + } + + public void updateLocations() { + this.syncLineLocations(); + } + + private boolean syncLocation() { + if (this.location == null) { + throw new IllegalStateException("Hologram location is null"); + } + + Location from = this.lastSyncedLocation; + Location to = this.location; + + boolean updated = false; + if (!Objects.equals(from, to)) { + this.syncLineLocations(); + if (this.locationUpdateHook != null) { + this.locationUpdateHook.handle(this, from, to); + } + updated = true; + } + + this.lastSyncedLocation = this.location; + this.locationSynced = true; + return updated; + } + + private void syncLineLocations() { + try { + this.linesLock.lock(); + + ImmutableList linesRef = this.lines; + + Location currentLocation = this.location.clone(); + for (int i = linesRef.size() - 1; i >= 0; i--) { + ItemHologramLine line = linesRef.get(i); + line.setLocation(currentLocation); + + currentLocation.add(0, line.getHeight().orElse(HologramLine.DEFAULT_LINE_HEIGHT), 0); + } + } finally { + this.linesLock.unlock(); + } + } + + private void sendSpawnPackets(List players) { + for (ItemHologramLine line : this.lines) { + line.sendSpawnPacket(players); + } + } + + private void sendMetaPackets(List players) { + for (ItemHologramLine line : this.lines) { + line.sendMetaPacket(players); + } + } + + private void sendTeleportPackets(List players) { + for (ItemHologramLine line : this.lines) { + line.sendTeleportPackets(players); + } + } + + private void sendKillPackets(List players) { + for (ItemHologramLine line : this.lines) { + line.sendKillPacket(players); + } + } + +} diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologramLine.java b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologramLine.java new file mode 100644 index 0000000..06b76e4 --- /dev/null +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologramLine.java @@ -0,0 +1,43 @@ +package com.tcoded.hologramlib.hologram; + +import com.tcoded.hologramlib.PlaceholderHandler; +import com.tcoded.hologramlib.hologram.meta.ItemDisplayMeta; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.function.BiConsumer; + +public abstract class ItemHologramLine extends HologramLine { + + private final ItemDisplayMeta meta; + + public ItemHologramLine(int entityId, PlaceholderHandler placeholderHandler) { + super(entityId, placeholderHandler, null); + this.meta = new ItemDisplayMeta(); + } + + public ItemDisplayMeta getMeta() { + return this.meta; + } + + public ItemStack getItemStack() { + return this.meta.getItemStack(); + } + + public void setItemStack(ItemStack itemStack) { + this.meta.setItemStack(itemStack); + } + + public void setItemStack(Material material) { + this.meta.setItemStack(new ItemStack(material)); + } + + @Override + protected abstract void sendMetaPacket(Collection players, @Nullable BiConsumer textParser); + +} diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java index 159a89c..535f84e 100644 --- a/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java @@ -13,7 +13,7 @@ import java.util.concurrent.locks.ReentrantLock; -public class TextHologram { +public class TextHologram implements Hologram { private final InternalIdType internalId; private final HologramPlayerTracker tracker; diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/meta/ItemDisplayMeta.java b/common/src/main/java/com/tcoded/hologramlib/hologram/meta/ItemDisplayMeta.java new file mode 100644 index 0000000..9901de8 --- /dev/null +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/meta/ItemDisplayMeta.java @@ -0,0 +1,22 @@ +package com.tcoded.hologramlib.hologram.meta; + +import org.bukkit.inventory.ItemStack; + +public class ItemDisplayMeta extends DisplayMeta { + + private ItemStack itemStack; + + public ItemDisplayMeta() { + super(); + this.itemStack = null; + } + + public ItemStack getItemStack() { + return itemStack; + } + + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + } + +} diff --git a/common/src/main/java/com/tcoded/hologramlib/manager/HologramManager.java b/common/src/main/java/com/tcoded/hologramlib/manager/HologramManager.java index 0ca6a3d..3b11654 100644 --- a/common/src/main/java/com/tcoded/hologramlib/manager/HologramManager.java +++ b/common/src/main/java/com/tcoded/hologramlib/manager/HologramManager.java @@ -2,9 +2,7 @@ import com.tcoded.hologramlib.HologramLib; import com.tcoded.hologramlib.PlaceholderHandler; -import com.tcoded.hologramlib.hologram.PacketPreprocessor; -import com.tcoded.hologramlib.hologram.TextHologram; -import com.tcoded.hologramlib.hologram.TextHologramLine; +import com.tcoded.hologramlib.hologram.*; import com.tcoded.hologramlib.utils.HologramLookupCache; import org.bukkit.Location; import org.bukkit.World; @@ -20,6 +18,7 @@ public abstract class HologramManager { private final HologramLib lib; private final Map> hologramsMap; + private final Map> itemHologramsMap; private HologramLookupCache cache; private PlayerManager playerManager; @@ -27,6 +26,7 @@ public abstract class HologramManager { protected HologramManager(HologramLib lib) { this.lib = lib; this.hologramsMap = new ConcurrentHashMap<>(); + this.itemHologramsMap = new ConcurrentHashMap<>(); } public void withCache(HologramLookupCache cache) { @@ -37,12 +37,18 @@ public void withCache(HologramLookupCache cache) { protected abstract TextHologramLine createNmsLine(PlaceholderHandler placeholderHandler, PacketPreprocessor packetPreprocessor); + protected abstract ItemHologramLine createNmsItemLine(PlaceholderHandler placeholderHandler); + protected abstract Location getPosUnsafe(Player player); public List> getHolograms() { return new ArrayList<>(this.hologramsMap.values()); } + public List> getItemHolograms() { + return new ArrayList<>(this.itemHologramsMap.values()); + } + /** * @param id Hologram ID @@ -62,13 +68,38 @@ public TextHologram createIfNotExists(InternalIdType id) { } /** - * Creates a new line + * Creates a new text line * @return TextHologramLine */ public TextHologramLine createLine() { return createNmsLine(this.lib.getPlaceholderHandler(), this.lib.getPacketPreprocessor()); } + /** + * Creates a new item line + * @return ItemHologramLine + */ + public ItemHologramLine createItemLine() { + return createNmsItemLine(this.lib.getPlaceholderHandler()); + } + + /** + * @param id Hologram ID + * @throws IllegalStateException If an item hologram with this ID already exits + * @return ItemHologram + */ + public ItemHologram createItem(InternalIdType id) { + return this.createItem(id, false); + } + + /** + * @param id Hologram ID + * @return ItemHologram or null + */ + public ItemHologram createItemIfNotExists(InternalIdType id) { + return this.createItem(id, true); + } + /** * @param id Hologram ID * @param silentFail Handle duplicate IDs gracefully @@ -90,7 +121,31 @@ private TextHologram create(InternalIdType id, boolean silentFai return hologram; } - private void onHoloPositionUpdate(TextHologram hologram, Location from, Location to) { + /** + * @param id Hologram ID + * @param silentFail Handle duplicate IDs gracefully + * @throws IllegalStateException If an item hologram with this ID already exits && silentFail is false + * @return ItemHologram or null + */ + private ItemHologram createItem(InternalIdType id, boolean silentFail) { + ItemHologram hologram = new ItemHologram<>(id); + + ItemHologram result = this.itemHologramsMap.compute(id, (id2, prevValue) -> prevValue == null ? hologram : prevValue); + if (result != hologram) { + if (silentFail) return null; + else throw new IllegalStateException("Item hologram with that ID already exists"); + } + + hologram.hookLocationUpdate(this::onItemHoloPositionUpdate); + return hologram; + } + + private void onHoloPositionUpdate(Hologram hologram, Location from, Location to) { + this.cache.onHoloPositionUpdate(hologram, from, to); + this.playerManager.updateTrackersNear(hologram, to); + } + + private void onItemHoloPositionUpdate(Hologram hologram, Location from, Location to) { this.cache.onHoloPositionUpdate(hologram, from, to); this.playerManager.updateTrackersNear(hologram, to); } @@ -99,14 +154,22 @@ public TextHologram getHologram(InternalIdType id) { return this.hologramsMap.get(id); } + public ItemHologram getItemHologram(InternalIdType id) { + return this.itemHologramsMap.get(id); + } + public void killAndRemove(InternalIdType id) { Optional.ofNullable(this.hologramsMap.remove(id)) - .ifPresent(this::tryHide); // despawn for all viewers + .ifPresent(this::tryHide); + Optional.ofNullable(this.itemHologramsMap.remove(id)) + .ifPresent(this::tryHideItem); } public void killAndRemoveAll() { - this.hologramsMap.values().forEach(this::tryHide); // despawn for all viewers + this.hologramsMap.values().forEach(this::tryHide); this.hologramsMap.clear(); + this.itemHologramsMap.values().forEach(this::tryHideItem); + this.itemHologramsMap.clear(); } public HologramLookupCache getLookupCache() { @@ -121,4 +184,8 @@ private void tryHide(@NotNull TextHologram hologram) { if (hologram.isVisible()) hologram.hide(); } + private void tryHideItem(@NotNull ItemHologram hologram) { + if (hologram.isVisible()) hologram.hide(); + } + } diff --git a/common/src/main/java/com/tcoded/hologramlib/manager/PlayerManager.java b/common/src/main/java/com/tcoded/hologramlib/manager/PlayerManager.java index 992a255..b9eceed 100644 --- a/common/src/main/java/com/tcoded/hologramlib/manager/PlayerManager.java +++ b/common/src/main/java/com/tcoded/hologramlib/manager/PlayerManager.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.manager; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import com.tcoded.hologramlib.tracker.PlayerHologramTracker; import com.tcoded.hologramlib.types.PlayerListSupplier; import com.tcoded.hologramlib.types.desync.DesyncAction; @@ -101,13 +101,13 @@ public void removeTracker(Player player) { PlayerHologramTracker tracker = this.trackers.remove(player.getUniqueId()); tracker.getTrackedArea().forEach(chunk -> { // Stop tracking player for each hologram in the chunk - Collection> holograms = this.hologramLookupCache.getHolograms(chunk); + Collection> holograms = this.hologramLookupCache.getHolograms(chunk); if (holograms == null) return; holograms.forEach(holo -> holo.getTracker().removeViewer(tracker)); }); } - public void updateTrackersNear(TextHologram hologram, Location to) { + public void updateTrackersNear(Hologram hologram, Location to) { // Build set of players to update Set playerHoloTrackers = new HashSet<>(); @@ -144,7 +144,7 @@ private Collection getOnlinePlayers() { return this.playerListSupplier.get(); } - private void markDesynced(Set playerHoloTrackers, TextHologram hologram) { + private void markDesynced(Set playerHoloTrackers, Hologram hologram) { for (PlayerHologramTracker playerHoloTracker : playerHoloTrackers) { // Sanity check Player player = playerHoloTracker.getPlayer(); diff --git a/common/src/main/java/com/tcoded/hologramlib/tracker/HologramPlayerTracker.java b/common/src/main/java/com/tcoded/hologramlib/tracker/HologramPlayerTracker.java index 9882d88..82f54a9 100644 --- a/common/src/main/java/com/tcoded/hologramlib/tracker/HologramPlayerTracker.java +++ b/common/src/main/java/com/tcoded/hologramlib/tracker/HologramPlayerTracker.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.tracker; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import com.tcoded.hologramlib.types.HologramPlayerAction; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -11,10 +11,10 @@ public class HologramPlayerTracker { - private final WeakReference> hologramRef; + private final WeakReference> hologramRef; private final Set viewers; - public HologramPlayerTracker(TextHologram hologram) { + public HologramPlayerTracker(Hologram hologram) { this.hologramRef = new WeakReference<>(hologram); this.viewers = createTrackedSet(); } @@ -48,12 +48,12 @@ public List getAllViewingPlayers() { public void addViewer(PlayerHologramTracker holoTracker) { this.viewers.add(holoTracker); - updatePlayer(holoTracker, TextHologram::show); + updatePlayer(holoTracker, Hologram::show); } public void removeViewer(PlayerHologramTracker holoTracker) { this.viewers.remove(holoTracker); - updatePlayer(holoTracker, TextHologram::hide); + updatePlayer(holoTracker, Hologram::hide); } public boolean isViewing(PlayerHologramTracker playerHoloTracker) { @@ -61,7 +61,7 @@ public boolean isViewing(PlayerHologramTracker playerHoloTracker) { } private void updatePlayer(PlayerHologramTracker holoTracker, HologramPlayerAction consumer) { - TextHologram holo = hologramRef.get(); + Hologram holo = hologramRef.get(); if (holo == null) return; if (!holo.isVisible()) return; diff --git a/common/src/main/java/com/tcoded/hologramlib/tracker/PlayerHologramTracker.java b/common/src/main/java/com/tcoded/hologramlib/tracker/PlayerHologramTracker.java index bbe8d77..8e1c4b3 100644 --- a/common/src/main/java/com/tcoded/hologramlib/tracker/PlayerHologramTracker.java +++ b/common/src/main/java/com/tcoded/hologramlib/tracker/PlayerHologramTracker.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.tracker; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import com.tcoded.hologramlib.types.chunk.ChunkArea; import com.tcoded.hologramlib.types.chunk.ChunkKey; import com.tcoded.hologramlib.utils.HologramLookupCache; @@ -24,7 +24,7 @@ public class PlayerHologramTracker { // Holograms which were specifically desynced due to an action outside the player's control // For example: a hologram was moved - private final Set> desyncedHolograms; + private final Set> desyncedHolograms; private ChunkArea trackedArea; @@ -75,7 +75,7 @@ public void checkDesyncedChunks() { public void checkDesyncedHolograms() { if (this.desyncedHolograms.isEmpty()) return; - Set> toCheck; + Set> toCheck; synchronized (this.desyncedHolograms) { toCheck = new HashSet<>(this.desyncedHolograms); @@ -87,13 +87,13 @@ public void checkDesyncedHolograms() { private void checkChunkHolograms(Set chunks) { for (ChunkKey chunk : chunks) { - Collection> holograms = this.hologramCache.getHolograms(chunk); + Collection> holograms = this.hologramCache.getHolograms(chunk); if (holograms == null) continue; holograms.forEach(this::checkHologram); } } - private void checkHologram(TextHologram hologram) { + private void checkHologram(Hologram hologram) { Player player = this.playerRef.get(); if (player == null) return; @@ -123,11 +123,11 @@ public ChunkArea getTrackedArea() { return this.trackedArea; } - public void markDesynced(TextHologram hologram) { + public void markDesynced(Hologram hologram) { this.desyncedHolograms.add(hologram); } - public void markDesynced(Collection> holograms) { + public void markDesynced(Collection> holograms) { this.desyncedHolograms.addAll(holograms); } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/HologramPlayerAction.java b/common/src/main/java/com/tcoded/hologramlib/types/HologramPlayerAction.java index dd54f6e..f441963 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/HologramPlayerAction.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/HologramPlayerAction.java @@ -1,10 +1,10 @@ package com.tcoded.hologramlib.types; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.entity.Player; import java.util.List; import java.util.function.BiConsumer; -public interface HologramPlayerAction extends BiConsumer, List> { +public interface HologramPlayerAction extends BiConsumer, List> { } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/LocationUpdateHook.java b/common/src/main/java/com/tcoded/hologramlib/types/LocationUpdateHook.java index 1c529f2..91e7f6f 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/LocationUpdateHook.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/LocationUpdateHook.java @@ -1,11 +1,11 @@ package com.tcoded.hologramlib.types; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.Location; @FunctionalInterface public interface LocationUpdateHook { - void handle(TextHologram hologram, Location from, Location to); + void handle(Hologram hologram, Location from, Location to); } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/desync/DesyncAction.java b/common/src/main/java/com/tcoded/hologramlib/types/desync/DesyncAction.java index ab94362..0dfe859 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/desync/DesyncAction.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/desync/DesyncAction.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.types.desync; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.Location; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -20,6 +20,6 @@ public interface DesyncAction { Location to(); @Nullable - Collection> manualHolos(); + Collection> manualHolos(); } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/desync/ManualDesyncAction.java b/common/src/main/java/com/tcoded/hologramlib/types/desync/ManualDesyncAction.java index 050bf16..c38c783 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/desync/ManualDesyncAction.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/desync/ManualDesyncAction.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.types.desync; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.Location; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; @@ -24,7 +24,7 @@ public ManualDesyncAction(Player player) { } @Override - public @Nullable Collection> manualHolos() { + public @Nullable Collection> manualHolos() { return null; } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/desync/MoveDesyncAction.java b/common/src/main/java/com/tcoded/hologramlib/types/desync/MoveDesyncAction.java index ccd218d..7106613 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/desync/MoveDesyncAction.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/desync/MoveDesyncAction.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.types.desync; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.Location; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; @@ -10,7 +10,7 @@ public record MoveDesyncAction(Player player, Location from, Location to) implements DesyncAction { @Override - public @Nullable Collection> manualHolos() { + public @Nullable Collection> manualHolos() { return null; } diff --git a/common/src/main/java/com/tcoded/hologramlib/types/desync/SpecificDesyncAction.java b/common/src/main/java/com/tcoded/hologramlib/types/desync/SpecificDesyncAction.java index 2945fd9..082ec32 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/desync/SpecificDesyncAction.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/desync/SpecificDesyncAction.java @@ -1,13 +1,13 @@ package com.tcoded.hologramlib.types.desync; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import org.bukkit.Location; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import java.util.Collection; -public record SpecificDesyncAction(Player player, Collection> manualHolos) implements DesyncAction { +public record SpecificDesyncAction(Player player, Collection> manualHolos) implements DesyncAction { @Override public @Nullable Location from() { diff --git a/common/src/main/java/com/tcoded/hologramlib/utils/HologramLookupCache.java b/common/src/main/java/com/tcoded/hologramlib/utils/HologramLookupCache.java index 6e68f73..3bd38f8 100644 --- a/common/src/main/java/com/tcoded/hologramlib/utils/HologramLookupCache.java +++ b/common/src/main/java/com/tcoded/hologramlib/utils/HologramLookupCache.java @@ -1,6 +1,6 @@ package com.tcoded.hologramlib.utils; -import com.tcoded.hologramlib.hologram.TextHologram; +import com.tcoded.hologramlib.hologram.Hologram; import com.tcoded.hologramlib.types.chunk.ChunkKey; import org.bukkit.Location; import org.jetbrains.annotations.Nullable; @@ -10,38 +10,40 @@ public class HologramLookupCache { - private final Map>> chunkCache; + private final Map>> chunkCache; public HologramLookupCache() { chunkCache = new ConcurrentHashMap<>(); } - public void onHoloPositionUpdate(TextHologram hologram, Location from, Location to) { + public void onHoloPositionUpdate(Hologram hologram, Location from, Location to) { // Remove the hologram from the previous chunk if (from != null) { ChunkKey fromChunk = new ChunkKey(from); - Set> holosRemove = chunkCache.get(fromChunk); - holosRemove.remove(hologram); + Set> holosRemove = chunkCache.get(fromChunk); + if (holosRemove != null) { + holosRemove.remove(hologram); + } } // Add the hologram to the new chunk ChunkKey toChunk = new ChunkKey(to); - Set> holosAdd = chunkCache.computeIfAbsent(toChunk, this::createHoloSet); + Set> holosAdd = chunkCache.computeIfAbsent(toChunk, this::createHoloSet); holosAdd.add(hologram); } - private Set> createHoloSet(Object ignore) { + private Set> createHoloSet(Object ignore) { return this.createHoloSet(); } - private Set> createHoloSet() { + private Set> createHoloSet() { return Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); } @Nullable - public Collection> getHolograms(ChunkKey key) { + public Collection> getHolograms(ChunkKey key) { return chunkCache.get(key); } diff --git a/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsHologramManager.java b/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsHologramManager.java index 485670f..49b99bd 100644 --- a/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsHologramManager.java +++ b/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsHologramManager.java @@ -2,10 +2,10 @@ import com.tcoded.hologramlib.HologramLib; import com.tcoded.hologramlib.PlaceholderHandler; +import com.tcoded.hologramlib.hologram.ItemHologramLine; import com.tcoded.hologramlib.hologram.PacketPreprocessor; import com.tcoded.hologramlib.hologram.TextHologramLine; import com.tcoded.hologramlib.manager.HologramManager; -import com.tcoded.hologramlib.hologram.TextHologram; import net.minecraft.world.entity.Entity; import org.bukkit.Location; import org.bukkit.World; @@ -29,6 +29,11 @@ protected TextHologramLine createNmsLine(PlaceholderHandler placeholderHandler, return NmsTextHologramLine.create(placeholderHandler, packetPreprocessor); } + @Override + protected ItemHologramLine createNmsItemLine(PlaceholderHandler placeholderHandler) { + return NmsItemHologramLine.create(placeholderHandler); + } + // Might become Folia incompatible at some point, so we build // a structure which allows for modification later @Override diff --git a/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsItemHologramLine.java b/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsItemHologramLine.java new file mode 100644 index 0000000..ae30ea4 --- /dev/null +++ b/nms_1_20_4/src/main/java/com/tcoded/hologramlib/nms/v1_20_4/NmsItemHologramLine.java @@ -0,0 +1,229 @@ +package com.tcoded.hologramlib.nms.v1_20_4; + +import com.google.common.collect.ImmutableList; +import com.mojang.math.Transformation; +import com.tcoded.hologramlib.HologramLib; +import com.tcoded.hologramlib.PlaceholderHandler; +import com.tcoded.hologramlib.hologram.ItemHologramLine; +import com.tcoded.hologramlib.hologram.meta.BillboardConstraints; +import com.tcoded.hologramlib.hologram.meta.ItemDisplayMeta; +import com.tcoded.hologramlib.types.math.Quaternion4F; +import com.tcoded.hologramlib.types.math.Vector3F; +import it.unimi.dsi.fastutil.ints.IntList; +import net.kyori.adventure.text.TextReplacementConfig; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.logging.Level; + +public class NmsItemHologramLine extends ItemHologramLine { + + private final Display.ItemDisplay parent; + + public static NmsItemHologramLine create(PlaceholderHandler placeholderHandler) { + // Accepts null values + // noinspection DataFlowIssue + Display.ItemDisplay parent = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, null); + return new NmsItemHologramLine(parent, placeholderHandler); + } + + public NmsItemHologramLine(Display.ItemDisplay parent, PlaceholderHandler placeholderHandler) { + super(parent.getId(), placeholderHandler); + this.parent = parent; + } + + @Override + public void sendSpawnPacket(Collection players) { + Location loc = getLocation(); + + ClientboundAddEntityPacket packet = new ClientboundAddEntityPacket(this.getEntityId(), + this.getEntityUuid(), + loc.getX(), + loc.getY(), + loc.getZ(), + loc.getPitch(), + loc.getYaw(), + EntityType.ITEM_DISPLAY, + 0, + Vec3.ZERO, + loc.getYaw() + ); + sendPacket(players, packet); + } + + @Override + public void sendSetPassengerPacket(Collection players, int baseEntityId) { + DummyEntity entity = new DummyEntity(baseEntityId); + entity.passengers = ImmutableList.of(this.parent); + + ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(entity); + sendPacket(players, packet); + } + + @Override + protected void sendMetaPacket(Collection players, @Nullable BiConsumer textParser) { + ItemDisplayMeta meta = this.getMeta(); + + // Set item + ItemStack itemStack = meta.getItemStack(); + if (itemStack != null) { + this.setNmsItemStack(itemStack); + } + + // Set display properties + if (meta.getInterpolationDelay() >= 0) this.setNmsInterpolationDelay(meta.getInterpolationDelay()); + if (meta.getTransformationInterpolationDuration() >= 0) this.setNmsTransformationInterpolationDuration(meta.getTransformationInterpolationDuration()); + if (meta.getPositionRotationInterpolationDuration() >= 0) this.setNmsPositionRotationInterpolationDuration(meta.getPositionRotationInterpolationDuration()); + if (meta.getTranslation() != null && meta.getLeftRotation() != null && meta.getScale() != null && meta.getRightRotation() != null) { + this.setNmsTransformation(meta.getTranslation(), meta.getLeftRotation(), meta.getScale(), meta.getRightRotation()); + } else { + HologramLib.logger().log(Level.WARNING, "WARNING: Some transformation values are null!", new Exception()); + } + if (meta.getBillboardConstraints() != null) this.setNmsBillboardConstraints(meta.getBillboardConstraints()); + if (meta.getBrightnessOverride() >= 0) this.setNmsBrightnessOverride(meta.getBrightnessOverride()); + if (meta.getViewRange() >= 0) this.setNmsViewRange(meta.getViewRange()); + if (meta.getShadowRadius() >= 0) this.setNmsShadowRadius(meta.getShadowRadius()); + if (meta.getWidth() >= 0) this.setNmsWidth(meta.getWidth()); + if (meta.getHeight() >= 0) this.setNmsHeight(meta.getHeight()); + if (meta.getGlowColorOverride() >= 0) this.setNmsGlowColorOverride(meta.getGlowColorOverride()); + + List> dataValues = buildDataValues(); + if (dataValues == null) return; + + ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket(this.getEntityId(), dataValues); + sendPacket(players, packet); + } + + private @Nullable List> buildDataValues() { + List> dataValues = this.parent.getEntityData().getNonDefaultValues(); + if (dataValues == null || dataValues.isEmpty()) { + HologramLib.logger().warning("No data values to send"); + return null; + } + return dataValues; + } + + @Override + public void sendKillPacket(Collection players) { + ClientboundRemoveEntitiesPacket packet = new ClientboundRemoveEntitiesPacket( + IntList.of(this.getEntityId()) + ); + sendPacket(players, packet); + } + + @Override + protected void sendTeleportPacket(Collection players, Location location) { + this.parent.setPos(location.getX(), location.getY(), location.getZ()); + + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(this.parent); + sendPacket(players, packet); + } + + private static void sendPacket(Collection players, Packet packet) { + for (Player player : players) { + sendPacket(player, packet); + } + } + + private static void sendPacket(Player player, Packet packet) { + if (!player.isOnline()) { + HologramLib.logger().warning("Player {NAME} is not online, cannot send packet".replace("{NAME}", player.getName())); + return; + } + + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + private void setNmsItemStack(ItemStack itemStack) { + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack); + this.parent.setItemStack(nmsItem); + } + + private void setNmsInterpolationDelay(int interpolationDelay) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformationInterpolationDelay(interpolationDelay); + } + + private void setNmsTransformationInterpolationDuration(int transformationInterpolationDuration) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformationInterpolationDuration(transformationInterpolationDuration); + } + + private void setNmsPositionRotationInterpolationDuration(int positionRotationInterpolationDuration) { + this.parent.getEntityData().set(Display.ItemDisplay.DATA_POS_ROT_INTERPOLATION_DURATION_ID, positionRotationInterpolationDuration); + } + + private void setNmsTransformation(Vector3F translation, Quaternion4F leftRotation, Vector3F scale, Quaternion4F rightRotation) { + Vector3f mojTranslation = new Vector3f(translation.getX(), translation.getY(), translation.getZ()); + Quaternionf mojLeftRotation = new Quaternionf(leftRotation.getX(), leftRotation.getY(), leftRotation.getZ(), leftRotation.getW()); + Vector3f mojScale = new Vector3f(scale.getX(), scale.getY(), scale.getZ()); + Quaternionf mojRightRotation = new Quaternionf(rightRotation.getX(), rightRotation.getY(), rightRotation.getZ(), rightRotation.getW()); + + Transformation transformation = new Transformation(mojTranslation, mojLeftRotation, mojScale, mojRightRotation); + + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformation(transformation); + } + + private void setNmsBillboardConstraints(BillboardConstraints billboardConstraints) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setBillboardConstraints(Display.BillboardConstraints.valueOf(billboardConstraints.name())); + } + + private void setNmsBrightnessOverride(int brightnessOverride) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setBrightnessOverride(new Brightness(brightnessOverride, brightnessOverride)); + } + + private void setNmsViewRange(float viewRange) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setViewRange(viewRange); + } + + private void setNmsShadowRadius(float shadowRadius) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setShadowRadius(shadowRadius); + } + + private void setNmsWidth(float width) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setWidth(width); + } + + private void setNmsHeight(float height) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setHeight(height); + } + + private void setNmsGlowColorOverride(int glowColorOverride) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setGlowColorOverride(glowColorOverride); + } + +} diff --git a/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsHologramManager.java b/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsHologramManager.java index 72e723d..7ad10f2 100644 --- a/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsHologramManager.java +++ b/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsHologramManager.java @@ -2,6 +2,7 @@ import com.tcoded.hologramlib.HologramLib; import com.tcoded.hologramlib.PlaceholderHandler; +import com.tcoded.hologramlib.hologram.ItemHologramLine; import com.tcoded.hologramlib.hologram.PacketPreprocessor; import com.tcoded.hologramlib.hologram.TextHologramLine; import com.tcoded.hologramlib.manager.HologramManager; @@ -28,6 +29,11 @@ protected TextHologramLine createNmsLine(PlaceholderHandler placeholderHandler, return NmsTextHologramLine.create(placeholderHandler, packetPreprocessor); } + @Override + protected ItemHologramLine createNmsItemLine(PlaceholderHandler placeholderHandler) { + return NmsItemHologramLine.create(placeholderHandler); + } + // Might become Folia incompatible at some point, so we build // a structure which allows for modification later @Override diff --git a/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsItemHologramLine.java b/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsItemHologramLine.java new file mode 100644 index 0000000..d285c18 --- /dev/null +++ b/nms_1_21_4/src/main/java/com/tcoded/hologramlib/nms/v1_21_4/NmsItemHologramLine.java @@ -0,0 +1,236 @@ +package com.tcoded.hologramlib.nms.v1_21_4; + +import com.google.common.collect.ImmutableList; +import com.mojang.math.Transformation; +import com.tcoded.hologramlib.HologramLib; +import com.tcoded.hologramlib.PlaceholderHandler; +import com.tcoded.hologramlib.hologram.ItemHologramLine; +import com.tcoded.hologramlib.hologram.meta.BillboardConstraints; +import com.tcoded.hologramlib.hologram.meta.ItemDisplayMeta; +import com.tcoded.hologramlib.types.math.Quaternion4F; +import com.tcoded.hologramlib.types.math.Vector3F; +import it.unimi.dsi.fastutil.ints.IntList; +import net.kyori.adventure.text.TextReplacementConfig; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.util.Brightness; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.logging.Level; + +public class NmsItemHologramLine extends ItemHologramLine { + + private final Display.ItemDisplay parent; + + public static NmsItemHologramLine create(PlaceholderHandler placeholderHandler) { + // Accepts null values + // noinspection DataFlowIssue + Display.ItemDisplay parent = new Display.ItemDisplay(EntityType.ITEM_DISPLAY, null); + return new NmsItemHologramLine(parent, placeholderHandler); + } + + public NmsItemHologramLine(Display.ItemDisplay parent, PlaceholderHandler placeholderHandler) { + super(parent.getId(), placeholderHandler); + this.parent = parent; + } + + @Override + public void sendSpawnPacket(Collection players) { + Location loc = getLocation(); + + ClientboundAddEntityPacket packet = new ClientboundAddEntityPacket(this.getEntityId(), + this.getEntityUuid(), + loc.getX(), + loc.getY(), + loc.getZ(), + loc.getPitch(), + loc.getYaw(), + EntityType.ITEM_DISPLAY, + 0, + Vec3.ZERO, + loc.getYaw() + ); + sendPacket(players, packet); + } + + @Override + public void sendSetPassengerPacket(Collection players, int baseEntityId) { + DummyEntity entity = new DummyEntity(baseEntityId); + entity.passengers = ImmutableList.of(this.parent); + + ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(entity); + sendPacket(players, packet); + } + + @Override + protected void sendMetaPacket(Collection players, @Nullable BiConsumer textParser) { + ItemDisplayMeta meta = this.getMeta(); + + // Set item + ItemStack itemStack = meta.getItemStack(); + if (itemStack != null) { + this.setNmsItemStack(itemStack); + } + + // Set display properties + if (meta.getInterpolationDelay() >= 0) this.setNmsInterpolationDelay(meta.getInterpolationDelay()); + if (meta.getTransformationInterpolationDuration() >= 0) this.setNmsTransformationInterpolationDuration(meta.getTransformationInterpolationDuration()); + if (meta.getPositionRotationInterpolationDuration() >= 0) this.setNmsPositionRotationInterpolationDuration(meta.getPositionRotationInterpolationDuration()); + if (meta.getTranslation() != null && meta.getLeftRotation() != null && meta.getScale() != null && meta.getRightRotation() != null) { + this.setNmsTransformation(meta.getTranslation(), meta.getLeftRotation(), meta.getScale(), meta.getRightRotation()); + } else { + HologramLib.logger().log(Level.WARNING, "WARNING: Some transformation values are null!", new Exception()); + } + if (meta.getBillboardConstraints() != null) this.setNmsBillboardConstraints(meta.getBillboardConstraints()); + if (meta.getBrightnessOverride() >= 0) this.setNmsBrightnessOverride(meta.getBrightnessOverride()); + if (meta.getViewRange() >= 0) this.setNmsViewRange(meta.getViewRange()); + if (meta.getShadowRadius() >= 0) this.setNmsShadowRadius(meta.getShadowRadius()); + if (meta.getWidth() >= 0) this.setNmsWidth(meta.getWidth()); + if (meta.getHeight() >= 0) this.setNmsHeight(meta.getHeight()); + if (meta.getGlowColorOverride() >= 0) this.setNmsGlowColorOverride(meta.getGlowColorOverride()); + + List> dataValues = buildDataValues(); + if (dataValues == null) return; + + ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket(this.getEntityId(), dataValues); + sendPacket(players, packet); + } + + private @Nullable List> buildDataValues() { + List> dataValues = this.parent.getEntityData().getNonDefaultValues(); + if (dataValues == null || dataValues.isEmpty()) { + HologramLib.logger().warning("No data values to send"); + return null; + } + return dataValues; + } + + @Override + public void sendKillPacket(Collection players) { + ClientboundRemoveEntitiesPacket packet = new ClientboundRemoveEntitiesPacket( + IntList.of(this.getEntityId()) + ); + sendPacket(players, packet); + } + + @Override + protected void sendTeleportPacket(Collection players, Location location) { + Vec3 prePos = this.parent.position(); + Vec3 newPos = new Vec3(location.getX(), location.getY(), location.getZ()); + Vec3 delta = newPos.subtract(prePos); + PositionMoveRotation posMovRot = new PositionMoveRotation(newPos, delta, this.parent.getYRot(), this.parent.getXRot()); + + this.parent.setPos(newPos); + + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket(this.getEntityId(), posMovRot, Relative.ALL, this.parent.onGround()); + sendPacket(players, packet); + } + + private static void sendPacket(Collection players, Packet packet) { + for (Player player : players) { + sendPacket(player, packet); + } + } + + private static void sendPacket(Player player, Packet packet) { + if (!player.isOnline()) { + HologramLib.logger().warning("Player {NAME} is not online, cannot send packet".replace("{NAME}", player.getName())); + return; + } + + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + private void setNmsItemStack(ItemStack itemStack) { + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack); + this.parent.setItemStack(nmsItem); + } + + private void setNmsInterpolationDelay(int interpolationDelay) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformationInterpolationDelay(interpolationDelay); + } + + private void setNmsTransformationInterpolationDuration(int transformationInterpolationDuration) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformationInterpolationDuration(transformationInterpolationDuration); + } + + private void setNmsPositionRotationInterpolationDuration(int positionRotationInterpolationDuration) { + this.parent.getEntityData().set(Display.ItemDisplay.DATA_POS_ROT_INTERPOLATION_DURATION_ID, positionRotationInterpolationDuration); + } + + private void setNmsTransformation(Vector3F translation, Quaternion4F leftRotation, Vector3F scale, Quaternion4F rightRotation) { + Vector3f mojTranslation = new Vector3f(translation.getX(), translation.getY(), translation.getZ()); + Quaternionf mojLeftRotation = new Quaternionf(leftRotation.getX(), leftRotation.getY(), leftRotation.getZ(), leftRotation.getW()); + Vector3f mojScale = new Vector3f(scale.getX(), scale.getY(), scale.getZ()); + Quaternionf mojRightRotation = new Quaternionf(rightRotation.getX(), rightRotation.getY(), rightRotation.getZ(), rightRotation.getW()); + + Transformation transformation = new Transformation(mojTranslation, mojLeftRotation, mojScale, mojRightRotation); + + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setTransformation(transformation); + } + + private void setNmsBillboardConstraints(BillboardConstraints billboardConstraints) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setBillboardConstraints(Display.BillboardConstraints.valueOf(billboardConstraints.name())); + } + + private void setNmsBrightnessOverride(int brightnessOverride) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setBrightnessOverride(new Brightness(brightnessOverride, brightnessOverride)); + } + + private void setNmsViewRange(float viewRange) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setViewRange(viewRange); + } + + private void setNmsShadowRadius(float shadowRadius) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setShadowRadius(shadowRadius); + } + + private void setNmsWidth(float width) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setWidth(width); + } + + private void setNmsHeight(float height) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setHeight(height); + } + + private void setNmsGlowColorOverride(int glowColorOverride) { + // noinspection UnnecessaryLocalVariable + Display requiredWorkaround = this.parent; + requiredWorkaround.setGlowColorOverride(glowColorOverride); + } + +} From a95c8ac17ce180c14be3cbff86154c7325af0978 Mon Sep 17 00:00:00 2001 From: ssquadteam Date: Fri, 16 Jan 2026 22:09:34 +0600 Subject: [PATCH 2/4] feat: add updateLocations method to Hologram interface and TextHologram implementation --- .../main/java/com/tcoded/hologramlib/hologram/Hologram.java | 2 ++ .../java/com/tcoded/hologramlib/hologram/TextHologram.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java index e045ca3..bec6093 100644 --- a/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java @@ -35,4 +35,6 @@ public interface Hologram { void updateMeta(); + void updateLocations(); + } diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java index 535f84e..e910faf 100644 --- a/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/TextHologram.java @@ -272,6 +272,10 @@ public void removeLine(int index) { if (holoVisible) this.sendTeleportPackets(this.tracker.getAllViewingPlayers()); } + public void updateLocations() { + this.syncLineLocations(); + } + /** * @return true if the location was updated, false if it was already synced */ From b49bcc06a8c483aa67576e776e724f256172f902 Mon Sep 17 00:00:00 2001 From: ssquadteam Date: Sat, 17 Jan 2026 03:08:26 +0600 Subject: [PATCH 3/4] feat: Convert Euler angles from degrees to radians internally within `Quaternion4F.fromEuler`. --- .../hologramlib/types/math/Quaternion4F.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/com/tcoded/hologramlib/types/math/Quaternion4F.java b/common/src/main/java/com/tcoded/hologramlib/types/math/Quaternion4F.java index 52f2a26..678886f 100644 --- a/common/src/main/java/com/tcoded/hologramlib/types/math/Quaternion4F.java +++ b/common/src/main/java/com/tcoded/hologramlib/types/math/Quaternion4F.java @@ -3,13 +3,19 @@ public class Quaternion4F { // Magic from Wikipedia + // Note: Input angles are in DEGREES, converted to radians internally public static Quaternion4F fromEuler(float roll, float yaw, float pitch) { - float cosRoll = (float) Math.cos(roll * 0.5); - float sinRoll = (float) Math.sin(roll * 0.5); - float cosYaw = (float) Math.cos(yaw * 0.5); - float sinYaw = (float) Math.sin(yaw * 0.5); - float cosPitch = (float) Math.cos(pitch * 0.5); - float sinPitch = (float) Math.sin(pitch * 0.5); + // Convert degrees to radians + float rollRad = (float) Math.toRadians(roll); + float yawRad = (float) Math.toRadians(yaw); + float pitchRad = (float) Math.toRadians(pitch); + + float cosRoll = (float) Math.cos(rollRad * 0.5); + float sinRoll = (float) Math.sin(rollRad * 0.5); + float cosYaw = (float) Math.cos(yawRad * 0.5); + float sinYaw = (float) Math.sin(yawRad * 0.5); + float cosPitch = (float) Math.cos(pitchRad * 0.5); + float sinPitch = (float) Math.sin(pitchRad * 0.5); return new Quaternion4F( sinRoll * cosPitch * cosYaw - cosRoll * sinPitch * sinYaw, From 0bd9674bb9002941ad611dc1f7d81233994a24c6 Mon Sep 17 00:00:00 2001 From: ssquadteam Date: Sun, 18 Jan 2026 17:28:24 +0600 Subject: [PATCH 4/4] fix: Calculate item hologram line locations relative to the base location instead of cumulatively. --- .../com/tcoded/hologramlib/hologram/ItemHologram.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java index 4a3c460..22cd58a 100644 --- a/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java +++ b/common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java @@ -308,12 +308,13 @@ private void syncLineLocations() { ImmutableList linesRef = this.lines; - Location currentLocation = this.location.clone(); + Location baseLocation = this.location.clone(); for (int i = linesRef.size() - 1; i >= 0; i--) { ItemHologramLine line = linesRef.get(i); - line.setLocation(currentLocation); - - currentLocation.add(0, line.getHeight().orElse(HologramLine.DEFAULT_LINE_HEIGHT), 0); + Location itemLocation = baseLocation.clone(); + double heightOffset = line.getHeight().orElse(0.0); + itemLocation.add(0, heightOffset, 0); + line.setLocation(itemLocation); } } finally { this.linesLock.unlock();