package net.citizensnpcs.npc.skin; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import com.google.common.base.Preconditions; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.util.NMS; /** * Handles and synchronizes add and remove packets for Player type NPC's in order to properly apply the NPC skin. * *
* Used as one instance per NPC entity. *
*/ public class SkinPacketTracker { private final SkinnableEntity entity; private final Map* Sends remove packets to all players. *
*/ public void onRemoveNPC() { isRemoved = true; Collection extends Player> players = Bukkit.getOnlinePlayers(); for (Player player : players) { if (player.hasMetadata("NPC")) { continue; } // send packet now and later to ensure removal from player list NMS.sendTabListRemove(player, entity.getBukkitEntity()); TAB_LIST_REMOVER.sendPacket(player, entity); } } /** * Invoke when the NPC entity is spawned. */ public void onSpawnNPC() { isRemoved = false; new BukkitRunnable() { @Override public void run() { if (!entity.getNPC().isSpawned()) return; double viewDistance = Setting.NPC_SKIN_VIEW_DISTANCE.asDouble(); updateNearbyViewers(viewDistance); } }.runTaskLater(CitizensAPI.getPlugin(), 15); } private void scheduleRemovePacket(PlayerEntry entry) { if (isRemoved || !CitizensAPI.hasImplementation() || !CitizensAPI.getPlugin().isEnabled() || !shouldRemoveFromTabList()) return; entry.removeTask = Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(), () -> TAB_LIST_REMOVER.sendPacket(entry.player, entity), PACKET_DELAY_REMOVE); } private void scheduleRemovePacket(PlayerEntry entry, int count) { entry.removeCount = count; scheduleRemovePacket(entry); } private boolean shouldRemoveFromTabList() { return entity.getNPC().data().get("removefromtablist", Setting.DISABLE_TABLIST.asBoolean()); } /** * Send skin related packets to all nearby players within the specified block radius. * * @param radius * The radius. */ public void updateNearbyViewers(double radius) { Player from = entity.getBukkitEntity(); CitizensAPI.getLocationLookup().getNearbyPlayers(from.getLocation(), radius).forEach(player -> { if (!player.canSee(from) || player.hasMetadata("NPC")) return; updateViewer(player); }); } /** * Send skin related packets to a player. * * @param player * The player. */ public void updateViewer(Player player) { Preconditions.checkNotNull(player); if (isRemoved || player.hasMetadata("NPC")) return; PlayerEntry entry = inProgress.get(player.getUniqueId()); if (entry != null) { entry.cancel(); } else { entry = new PlayerEntry(player); } TAB_LIST_REMOVER.cancelPackets(player, entity); inProgress.put(player.getUniqueId(), entry); skin.apply(entity); if (NMS.sendTabListAdd(player, entity.getBukkitEntity())) { scheduleRemovePacket(entry, Setting.TABLIST_REMOVE_PACKET_DELAY.asTicks()); } } private static class PlayerEntry { Player player; int removeCount; BukkitTask removeTask; PlayerEntry(Player player) { this.player = player; } // cancel previous packet tasks so they do not interfere with // new tasks void cancel() { if (removeTask != null) { removeTask.cancel(); } removeCount = 0; } } private static class PlayerListener implements Listener { @EventHandler private void onPlayerQuit(PlayerQuitEvent event) { // this also causes any entries in the "inProgress" field to // be removed. TAB_LIST_REMOVER.cancelPackets(event.getPlayer()); } } private static PlayerListener LISTENER; private static int PACKET_DELAY_REMOVE = 2; private static TabListRemover TAB_LIST_REMOVER = new TabListRemover(); }