Skip to content

Instantly share code, notes, and snippets.

@aadnk
Last active April 25, 2024 22:47
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save aadnk/5871793 to your computer and use it in GitHub Desktop.
Save aadnk/5871793 to your computer and use it in GitHub Desktop.
Hide or show entities
package com.comphenix.example;
import static com.comphenix.protocol.PacketType.Play.Server.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Map;
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 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;
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_VELOCITY, REL_ENTITY_MOVE, ENTITY_LOOK, ENTITY_MOVE_LOOK, ENTITY_MOVE_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
};
/**
* 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 entity - 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();
for (Map<Integer, Boolean> maps : observerEntityMap.rowMap().values()) {
maps.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 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.
* <p>
* 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.");
}
/**
* 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;
}
}
}
@Ixf1nity
Copy link

thank you <3

@ashfn
Copy link

ashfn commented Sep 2, 2022

❤️

@DevGizzmo
Copy link

Hello, i don't know if u awnser just I can't seem to hide a potion from a player when another player throws it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment