Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: item lines not showing to players upon joining (#204) #205

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,10 +45,12 @@ public final class DecentHolograms {

private final JavaPlugin plugin;
private HologramManager hologramManager;
private HologramLineDisplayHandler lineDisplayHandler;
private CommandManager commandManager;
private FeatureManager featureManager;
private AnimationManager animationManager;
private PacketListener packetListener;
private PlayerListener playerListener;
private Ticker ticker;
private boolean updateAvailable;

Expand Down Expand Up @@ -80,13 +83,14 @@ 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);
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
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/eu/decentsoftware/holograms/api/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -42,6 +44,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-session-ticks-item-line", min = 0)
public static int DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE = 40;

public static Map<String, String> CUSTOM_REPLACEMENTS = ImmutableMap.<String, String>builder()
.put("[x]", "\u2588")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ public void show(Player... players) {
if (parent != null && parent.getParent().isHideState(player)) {
continue;
}

DECENT_HOLOGRAMS.getLineDisplayHandler().removeFromQueue(this, player);

if (!isVisible(player) && canShow(player) && isInDisplayRange(player)) {
switch (type) {
case TEXT:
Expand All @@ -418,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();
Expand All @@ -447,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.
*
Expand Down Expand Up @@ -519,6 +539,7 @@ public void updateAnimations(Player... players) {
public void hide(Player... players) {
List<Player> 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());
}
Expand Down Expand Up @@ -565,10 +586,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));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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;
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<LineDisplayEntry> 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<UUID> playersUpdated = new LinkedHashSet<>(); // We only want to send the player one item per tick
final Set<LineDisplayEntry> reQueue = new LinkedHashSet<>();

while (!queue.isEmpty()) {
LineDisplayEntry poll = queue.poll();

if (poll == null) {
continue;
}

if (System.currentTimeMillis() - poll.getTimestamp() > TTL) {
continue;
}

Player player = Bukkit.getPlayer(poll.getPlayerId());

if (player == null) {
continue;
}

if (playersUpdated.contains(player.getUniqueId())) {
reQueue.add(poll);
continue;
}

HologramLine line = poll.getLine();

if (!line.isVisible(player)) {
continue;
}

if (canDisplay(player)) {
line.showItem(player);
playersUpdated.add(player.getUniqueId());
} else {
reQueue.add(poll);
}
}

queue.addAll(reQueue);
playersUpdated.clear();
reQueue.clear();

if (queue.size() >= MAX_QUEUE_SIZE) {
queue.clear();
}
}

private boolean canDisplay(Player player) {
return DecentHologramsAPI.get().getPlayerListener().getTicksSinceLogin(player) >= Settings.DEFAULT_MINIMUM_SESSION_TICKS_ITEM_LINE;
}

@Getter
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;
this.playerId = playerId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,6 +69,13 @@ public void updateVisibility(@NonNull Player player) {
}
}

/**
* 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 void updateVisibility(@NonNull Player player, @NonNull Hologram hologram) {
if (hologram.isDisabled()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UUID, Long> playerLoginTime = new ConcurrentHashMap<>(); // Hologram lines can be updated asynchronously

public PlayerListener(DecentHolograms decentHolograms) {
this.decentHolograms = decentHolograms;
Expand All @@ -24,18 +29,20 @@ 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);
}

playerLoginTime.put(player.getUniqueId(), System.currentTimeMillis());
}

@EventHandler
public void onQuit(PlayerQuitEvent e) {
Player player = e.getPlayer();
S.async(() -> decentHolograms.getHologramManager().onQuit(player));
decentHolograms.getPacketListener().unhook(player);
playerLoginTime.remove(player.getUniqueId());
}

@EventHandler
Expand All @@ -59,4 +66,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;
}
}