diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 9747a8866b..505c58db49 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -8,16 +8,19 @@ import io.netty.buffer.Unpooled; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.ref.WeakReference; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -63,10 +66,13 @@ import org.bukkit.inventory.InventoryView.Property; import org.bukkit.map.MapCursor; import org.bukkit.map.MapView; import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.IllegalPluginAccessException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.scoreboard.Scoreboard; +import javax.annotation.Nullable; + @DelegateDeserialization(CraftOfflinePlayer.class) public class CraftPlayer extends CraftHumanEntity implements Player { private long firstPlayed = 0; @@ -74,7 +80,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private boolean hasPlayedBefore = false; private final ConversationTracker conversationTracker = new ConversationTracker(); private final Set channels = new HashSet(); - private final Set hiddenPlayers = new HashSet(); + private final Map>> hiddenPlayers = new HashMap<>(); + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); private int hash = 0; private double health = 20; private boolean scaledHealth = false; @@ -905,18 +912,45 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } } + @Nullable + private static WeakReference getPluginWeakReference(@Nullable Plugin plugin) { + return (plugin == null) ? null : pluginWeakReferences.computeIfAbsent(plugin, WeakReference::new); + } + @Override + @Deprecated public void hidePlayer(Player player) { + hidePlayer0(null, player); + } + + @Override + public void hidePlayer(Plugin plugin, Player player) { + Validate.notNull(plugin, "Plugin cannot be null"); + Validate.isTrue(plugin.isEnabled(), "Plugin attempted to hide player while disabled"); + + hidePlayer0(plugin, player); + } + + private void hidePlayer0(@Nullable Plugin plugin, Player player) { Validate.notNull(player, "hidden player cannot be null"); if (getHandle().playerConnection == null) return; if (equals(player)) return; - if (hiddenPlayers.contains(player.getUniqueId())) return; - hiddenPlayers.add(player.getUniqueId()); + + Set> hidingPlugins = hiddenPlayers.get(player.getUniqueId()); + if (hidingPlugins != null) { + // Some plugins are already hiding the player. Just mark that this + // plugin wants the player hidden too and end. + hidingPlugins.add(getPluginWeakReference(plugin)); + return; + } + hidingPlugins = new HashSet<>(); + hidingPlugins.add(getPluginWeakReference(plugin)); + hiddenPlayers.put(player.getUniqueId(), hidingPlugins); // Remove this player from the hidden player's EntityTrackerEntry EntityTracker tracker = ((WorldServer) entity.world).tracker; EntityPlayer other = ((CraftPlayer) player).getHandle(); - EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(other.getId()); + EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId()); if (entry != null) { entry.clear(getHandle()); } @@ -928,11 +962,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } @Override + @Deprecated public void showPlayer(Player player) { + showPlayer0(null, player); + } + + @Override + public void showPlayer(Plugin plugin, Player player) { + Validate.notNull(plugin, "Plugin cannot be null"); + // Don't require that plugin be enabled. A plugin must be allowed to call + // showPlayer during its onDisable() method. + showPlayer0(plugin, player); + } + + private void showPlayer0(@Nullable Plugin plugin, Player player) { Validate.notNull(player, "shown player cannot be null"); if (getHandle().playerConnection == null) return; if (equals(player)) return; - if (!hiddenPlayers.contains(player.getUniqueId())) return; + + Set> hidingPlugins = hiddenPlayers.get(player.getUniqueId()); + if (hidingPlugins == null) { + return; // Player isn't hidden + } + hidingPlugins.remove(getPluginWeakReference(plugin)); + if (!hidingPlugins.isEmpty()) { + return; // Some other plugins still want the player hidden + } hiddenPlayers.remove(player.getUniqueId()); EntityTracker tracker = ((WorldServer) entity.world).tracker; @@ -940,7 +995,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other)); - EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(other.getId()); + EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId()); if (entry != null && !entry.trackedPlayers.contains(getHandle())) { entry.updatePlayer(getHandle()); } @@ -952,7 +1007,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public boolean canSee(Player player) { - return !hiddenPlayers.contains(player.getUniqueId()); + return !hiddenPlayers.containsKey(player.getUniqueId()); } @Override