Solved [1.8.8] Player#hidePlayer not working on connect

Discussion in 'Spigot Plugin Development' started by Mister_2, Jun 3, 2017.

  1. Basically, as the headline suggests, I can't get the #hidePlayer method to work on connect. I did some research and found that some people had been experiencing this issue, but it appeared to be fixed.

    In an attempt to see if maybe I was doing something wrong, even though I have used this exact method about a million times before, I used this:

    Code (Text):
        @EventHandler
        public void onPlayerJoin(PlayerJoinEvent event)
        {
            for (Player player : Bukkit.getOnlinePlayers())
            {
                if (event.getPlayer() == player)
                {
                    continue;
                }

                Server.debug("Hiding " + player.getName() + " from " + event.getPlayer() + "!");

                event.getPlayer().hidePlayer(player);
                player.hidePlayer(event.getPlayer());
            }
        }
    Still the same issue. If I delay it about 15 ticks, #hidePlayer works, but this way the players name appears on the tab list for a brief amount of time, which is not desirable.

    The debug message appears, so I know the method is being called.

    Anyone experienced something similar and know how to fix it?

    EDIT: Running the newest 1.8.8 version, if anyone was wondering.
     
    #1 Mister_2, Jun 3, 2017
    Last edited: Jun 3, 2017
    • Funny Funny x 1
  2. I think this what you wrote is correct. The only way to fix it is to delay this (what you already tried).
    Actually there is another option: You could also give the Player the invisibility effect.
     
  3. Use the entityhider class:

    Code (Text):

    import com.comphenix.protocol.PacketType;
    import com.comphenix.protocol.ProtocolLibrary;
    import com.comphenix.protocol.ProtocolManager;
    import com.comphenix.protocol.events.PacketAdapter;
    import com.comphenix.protocol.events.PacketContainer;
    import com.comphenix.protocol.events.PacketEvent;
    import com.google.common.base.Preconditions;
    import com.google.common.collect.HashBasedTable;
    import com.google.common.collect.Table;
    import org.bukkit.Bukkit;
    import org.bukkit.entity.Entity;
    import org.bukkit.entity.Player;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.HandlerList;
    import org.bukkit.event.Listener;
    import org.bukkit.event.entity.EntityDeathEvent;
    import org.bukkit.event.player.PlayerQuitEvent;
    import org.bukkit.event.world.ChunkUnloadEvent;
    import org.bukkit.plugin.Plugin;

    import java.lang.reflect.InvocationTargetException;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.Map;

    import static com.comphenix.protocol.PacketType.Play.Server.*;

    public class EntityHider implements Listener {

        protected Table<Integer, Integer, Boolean> observerEntityMap = HashBasedTable.create();

        // Packets that update remote player entities
        private static final PacketType[] ENTITY_PACKETS = {
                ENTITY_EQUIPMENT, BED, ANIMATION, NAMED_ENTITY_SPAWN,
                COLLECT, SPAWN_ENTITY, SPAWN_ENTITY_LIVING, SPAWN_ENTITY_PAINTING, SPAWN_ENTITY_EXPERIENCE_ORB,
                ENTITY_LOOK, ENTITY_TELEPORT, ENTITY_HEAD_ROTATION,
                ENTITY_STATUS, ATTACH_ENTITY, ENTITY_METADATA, ENTITY_EFFECT, REMOVE_ENTITY_EFFECT,
                BLOCK_BREAK_ANIMATION

                // We don't handle DESTROY_ENTITY though
                // Removed for knockback: ENTITY_VELOCITY, REL_ENTITY_MOVE,
        };

        /**
         * The current entity visibility policy.
         * @author Kristian
         */
        public enum Policy {
            /**
             * All entities are invisible by default. Only entities specifically made visible may be seen.
             */
            WHITELIST,

            /**
             * All entities are visible by default. An entity can only be hidden explicitly.
             */
            BLACKLIST,
        }

        private ProtocolManager manager;

        // Listeners
        private Listener bukkitListener;
        private PacketAdapter protocolListener;

        // Current policy
        protected final Policy policy;

        /**
         * Construct a new entity hider.
         * @param plugin - the plugin that controls this entity hider.
         * @param policy - the default visibility policy.
         */
        public EntityHider(Plugin plugin, Policy policy) {
            Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");

            // Save policy
            this.policy = policy;
            this.manager = ProtocolLibrary.getProtocolManager();

            // Register events and packet listener
            plugin.getServer().getPluginManager().registerEvents(
                    bukkitListener = constructBukkit(), plugin);
            manager.addPacketListener(
                    protocolListener = constructProtocol(plugin));
        }

        /**
         * Set the visibility status of a given entity for a particular observer.
         * @param observer - the observer player.
         * @param entityID - ID of the entity that will be hidden or made visible.
         * @param visible - TRUE if the entity should be made visible, FALSE if not.
         * @return TRUE if the entity was visible before this method call, FALSE otherwise.
         */
        protected boolean setVisibility(Player observer, int entityID, boolean visible) {
            switch (policy) {
                case BLACKLIST:
                    // Non-membership means they are visible
                    return !setMembership(observer, entityID, !visible);
                case WHITELIST:
                    return setMembership(observer, entityID, visible);
                default :
                    throw new IllegalArgumentException("Unknown policy: " + policy);
            }
        }

        /**
         * Add or remove the given entity and observer entry from the table.
         * @param observer - the player observer.
         * @param entityID - ID of the entity.
         * @param member - TRUE if they should be present in the table, FALSE otherwise.
         * @return TRUE if they already were present, FALSE otherwise.
         */
        // Helper method
        protected boolean setMembership(Player observer, int entityID, boolean member) {
            if (member) {
                return observerEntityMap.put(observer.getEntityId(), entityID, true) != null;
            } else {
                return observerEntityMap.remove(observer.getEntityId(), entityID) != null;
            }
        }

        /**
         * Determine if the given entity and observer is present in the table.
         * @param observer - the player observer.
         * @param entityID - ID of the entity.
         * @return TRUE if they are present, FALSE otherwise.
         */
        protected boolean getMembership(Player observer, int entityID) {
            return observerEntityMap.contains(observer.getEntityId(), entityID);
        }

        /**
         * Determine if a given entity is visible for a particular observer.
         * @param observer - the observer player.
         * @param entityID -  ID of the entity that we are testing for visibility.
         * @return TRUE if the entity is visible, FALSE otherwise.
         */
        protected boolean isVisible(Player observer, int entityID) {
            // If we are using a whitelist, presence means visibility - if not, the opposite is the case
            boolean presence = getMembership(observer, entityID);

            return policy == Policy.WHITELIST ? presence : !presence;
        }

        /**
         * Remove the given entity from the underlying map.
         * @param entity - the entity to remove.
         * @param destroyed - TRUE if the entity was killed, FALSE if it is merely unloading.
         */
        protected void removeEntity(Entity entity, boolean destroyed) {
            int entityID = entity.getEntityId();

            Iterator<Map<Integer, Boolean>> iterator = observerEntityMap.rowMap().values().iterator();

            while(iterator.hasNext()) {
                ((Map<Integer, Boolean>)iterator.next()).remove(entityID);
            }
        }

        /**
         * Invoked when a player logs out.
         * @param player - the player that jused logged out.
         */
        protected void removePlayer(Player player) {
            // Cleanup
            observerEntityMap.rowMap().remove(player.getEntityId());
        }

        /**
         * Construct the Bukkit event listener.
         * @return Our listener.
         */
        private Listener constructBukkit() {
            return new Listener() {
                @EventHandler
                public void onEntityDeath(EntityDeathEvent e) {
                    removeEntity(e.getEntity(), true);
                }

                @EventHandler
                public void onChunkUnload(ChunkUnloadEvent e) {
                    for (Entity entity : e.getChunk().getEntities()) {
                        removeEntity(entity, false);
                    }
                }

                @EventHandler
                public void onPlayerQuit(PlayerQuitEvent e) {
                    removePlayer(e.getPlayer());
                }
            };
        }

        /**
         * Construct the packet listener that will be used to intercept every entity-related packet.
         * @param plugin - the parent plugin.
         * @return The packet listener.
         */
        private PacketAdapter constructProtocol(Plugin plugin) {
            return new PacketAdapter(plugin, ENTITY_PACKETS) {
                @Override
                public void onPacketSending(PacketEvent event) {
                    int entityID = event.getPacket().getIntegers().read(0);

                    // See if this packet should be cancelled
                    if (!isVisible(event.getPlayer(), entityID)) {
                        event.setCancelled(true);
                    }
                }
            };
        }

        /**
         * Toggle the visibility status of an entity for a player.
         * <p>
         * If the entity is visible, it will be hidden. If it is hidden, it will become visible.
         * @param observer - the player observer.
         * @param entity - the entity to toggle.
         * @return TRUE if the entity was visible before, FALSE otherwise.
         */
        public final boolean toggleEntity(Player observer, Entity entity) {
            if (isVisible(observer, entity.getEntityId())) {
                return hideEntity(observer, entity);
            } else {
                return !showEntity(observer, entity);
            }
        }

        /**
         * Allow the observer to see an entity with a opacity effect.
         * @param observer - the observer.
         * @param entity - the entity to show.
         * @return TRUE if the entity was hidden before, FALSE otherwise.
         */
        public final boolean showFadedEntity(Player observer, Entity entity) {
            validate(observer, entity);
            boolean hiddenBefore = !setVisibility(observer, entity.getEntityId(), true);

            if (manager != null && hiddenBefore) {
                manager.updateEntity(entity, Arrays.asList(observer));
            }

            return hiddenBefore;
        }

        /**
         * Allow the observer to see an entity that was previously hidden.
         * @param observer - the observer.
         * @param entity - the entity to show.
         * @return TRUE if the entity was hidden before, FALSE otherwise.
         */
        public final boolean showEntity(Player observer, Entity entity) {
            validate(observer, entity);
            boolean hiddenBefore = !setVisibility(observer, entity.getEntityId(), true);

            // Resend packets
            if (manager != null && hiddenBefore) {
                manager.updateEntity(entity, Arrays.asList(observer));
            }

            return hiddenBefore;
        }

        /**
         * Prevent the observer from seeing a given entity.
         * @param observer - the player observer.
         * @param entity - the entity to hide.
         * @return TRUE if the entity was previously visible, FALSE otherwise.
         */
        public final boolean hideEntity(Player observer, Entity entity) {
            validate(observer, entity);
            boolean visibleBefore = setVisibility(observer, entity.getEntityId(), false);

            if (visibleBefore) {
                PacketContainer destroyEntity = new PacketContainer(ENTITY_DESTROY);
                destroyEntity.getIntegerArrays().write(0, new int[] { entity.getEntityId() });

                // Make the entity disappear
                try {
                    manager.sendServerPacket(observer, destroyEntity);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot send server packet.", e);
                }
            }
            return visibleBefore;
        }

        /**
         * Determine if the given entity has been hidden from an observer.
         * Note that the entity may very well be occluded or out of range from the perspective
         * of the observer. This method simply checks if an entity has been completely hidden
         * for that observer.
         * @param observer - the observer.
         * @param entity - the entity that may be hidden.
         * @return TRUE if the player may see the entity, FALSE if the entity has been hidden.
         */
        public final boolean canSee(Player observer, Entity entity) {
            validate(observer, entity);

            return isVisible(observer, entity.getEntityId());
        }

        // For valdiating the input parameters
        private void validate(Player observer, Entity entity) {
            Preconditions.checkNotNull(observer, "observer cannot be NULL.");
            Preconditions.checkNotNull(entity, "entity cannot be NULL.");
        }

        /**
         * Show all online players to the given player.
         * @param player
         */
        public void showAllPlayers(Player player) {
            for(Player p : Bukkit.getOnlinePlayers()) {
                this.showEntity(player, p);
                this.showEntity(p, player);
            }
        }

        /**
         * Hide all online players to the given player.
         * @param player
         */
        public void hideAllPlayers(Player player) {
            for(Player p : Bukkit.getOnlinePlayers()) {
                if(p == player) continue;

                this.hideEntity(player, p);
                this.hideEntity(p, player);
            }
        }

        /**
         * Retrieve the current visibility policy.
         * @return The current visibility policy.
         */
        public Policy getPolicy() {
            return policy;
        }

        public void close() {
            if (manager != null) {
                HandlerList.unregisterAll(bukkitListener);
                manager.removePacketListener(protocolListener);
                manager = null;
            }
        }

    }
     
  4. What's the point of this? Also, using this method that you attempted works for me every single time.

    Code (Text):
    if (event.getPlayer() == player)
                {
                    continue;
                }
     
  5. Already tried that. The issue here is that this doesn't remove the player from the tab list, which again, not desirable. By reading some of the source from CraftPlayer.class we can see that it is not very complicated. I did attempt to combine this with a custom implementation of my own player hiding system without luck (Where I utilized the EntityHider + some packetry).

    Why would I wan't to hide a player from themselves? Also, if you read what I wrote, it doesn't work, which is why I am asking. I know it should work, I have used this exact code before...
     
  6. Ugh, you can't hide a player from themselves. It doesn't work. Just use this.
    Code (Text):
    // inside of PlayerJoinEvent (Event e)

    for(Player all : Bukkit.getServer().getOnlinePlayers()) {
        all.hidePlayer(e.getPlayer());
    }
    If you absolutely must, just encompass that with this:
    Code (Text):
    if(all != e.getPlayer())
    It doesn't help that the way you're coding is so painful to look at.
     
  7. Ok, 1: I am not hiding a player from themselves, thus the if statement. No point in calling the method to have it check, I know it won't work. Faster to check once than have it check twice, since I am hiding the players from each other.

    2: This is the code style used in the project I am working on, not something decided by me.

    You obviously haven't read what I wrote properly. The code I provided is not what I wish to use. It was used to test what I suspected: It simply doesn't work to hide players on connect. This is the issue. I solved the issue by delaying it by 15 ticks, but, this is of course not desirable because you get the player showing in the tab list.

    My question is; Does anyone know how to actually fix this?

    Also:

    You suggest that I use nested if-statements. This ends up looking awful if you use multiple. Escape clauses are common practice in more professional environments. Anyway, I am not here to debate coding styles, I am trying to figure out why this doesn't work.
     
  8. Just so you know, it can and often does work when you use it in a joinevent. You just need to use it correctly.
     
  9. Dude, please, I KNOW it should work. This is why I am so confused as to why it doesn't.
     
    • Funny Funny x 1
  10. If you just tried the code I gave you it would work. Unless you're using it inside of some method that's causing it to malfunction, or you have something else that's overriding it, it should work fine.
     
  11. Exactly, it should work. It doesn't, that is the issue.
     
    • Funny Funny x 1
  12. First off. Calm down. He hasn't understood because you haven't explained properly. He's given you code, don't get angry.

    Is it vital you use 1.8.8?
     
  13. Mas

    Mas

    Try delaying by only 1 tick. It will still appear on tab for a very brief period of time (0.05 sec) but that shouldn't be long enough for anyone to really notice it or read the name.
     
  14. Tried that too (If you see my post, I wrote 15 ticks worked). I am going to check for usages of showPlayer by other devs on the project, this may actually be it.

    Thanks for the to help anyway! :)

    Ok, I don't understand how I could've been more clear, not really mad either, sorry if it appeared that way, but anyway, I believe I solved it. Turns out on loading of preferences there was a #showPlayer call (Me being dumb I didn't realise this). Of course, this is nothing any of you could've known about, so my bad.