package net.bitjump.bukkit.bitlib.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import net.bitjump.bukkit.bitlib.util.classes.PlayerMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
/**
*
* Assorted methods for manipulating packets to spawn fake Ender Dragons and show players a
* status bar at the top of the screen using their health bar. This class uses reflection, so
* even though it accesses NSM methods it should be version-safe (assuming the names of classes
* don't change).
*
* This is a clean-up/fix-up/refactoring of SoThatsIt's code, which originally did nearly the
* same thing, but was less readable, had less features, and was broken in some places. It also
* uses a trimmed down version of my own {@link PlayerMap} class to store the fake dragons.
*
* @author AmoebaMan
*
*/
public class StatusBarUtil {
private static PlayerMap<FakeDragon> DRAGONS = new PlayerMap<FakeDragon>();
/**
* Checks to see if the player is currently being displayed a status bar via fake Ender Dragon.
* <br><br>
* This may sometimes return a false positive. Specifically, if a player is sent a fake dragon, and
* subsequently logs off and back on and the bar is not restored, the record of the dragon will remain
* here even though the client no longer has the entity. To avoid this, be sure to remove the bar
* manually using {@link #removeStatusBar(Player)} when the player leaves the server
* ({@link org.bukkit.event.player.PlayerQuitEvent} and {@link org.bukkit.event.player.PlayerKickEvent})
*
* @param player a player
* @return true if this API has a record of the player being sent a bar
*/
public static boolean hasStatusBar(Player player){
return DRAGONS.containsKey(player) && DRAGONS.get(player) != null;
}
/**
* Removes a player's status bar by destroying their fake dragon (if they have one).
*
* @param player a player
*/
public static void removeStatusBar(Player player){
if(hasStatusBar(player)){
sendPacket(player, DRAGONS.get(player).getDestroyPacket());
DRAGONS.remove(player);
}
}
/**
* Sets a player's status bar to display a specific message and fill amount. The fill amount is in
* decimal percent (i.e. 1 = 100%, 0 = 0%, 0.5 = 50%, 0.775 = 77.5%, etc.).
* <br><br>
* <code>text</code> is limited to 64 characters, and <code>percent</code> must be greater than zero
* and less than or equal to one. If either argument is outside its constraints, it will be quietly
* trimmed to match.
*
* @param player a player
* @param text some text with 64 characters or less
* @param percent a decimal percent in the range (0,1]
*/
public static void setStatusBar(Player player, String text, float percent) {
FakeDragon dragon = DRAGONS.containsKey(player) ? DRAGONS.get(player) : null;
if(text.length() > 64)
text = text.substring(0, 63);
if(percent > 1.0f)
percent = 1.0f;
if(percent < 0.05f)
percent = 0.05f;
if (text.isEmpty() && dragon != null)
removeStatusBar(player);
if (dragon == null) {
dragon = new FakeDragon(player.getLocation().add(0, -200, 0), text, percent);
sendPacket(player, dragon.getSpawnPacket());
DRAGONS.put(player, dragon);
}
else {
dragon.setName(text);
dragon.setHealth(percent);
sendPacket(player, dragon.getMetaPacket(dragon.getWatcher()));
sendPacket(player, dragon.getTeleportPacket(player.getLocation().add(0, -200, 0)));
}
}
/**
* Removes the status bar for all players on the server. See {@link #removeStatusBar(Player)}.
*/
public static void removeAllStatusBars(){
for(Player each : Bukkit.getOnlinePlayers())
removeStatusBar(each);
}
/**
* Sets the status bar for all players on the server. See {@link #setStatusBar(Player, String, float)}.
* @param text some text with 64 characters or less
* @param percent a decimal percent in the range (0,1]
*/
public static void setAllStatusBars(String text, float percent){
for(Player each : Bukkit.getOnlinePlayers())
setStatusBar(each, text, percent);
}
private static void sendPacket(Player player, Object packet) {
try {
Object nmsPlayer = ReflectionUtils.getHandle(player);
Field connectionField = nmsPlayer.getClass().getField("playerConnection");
Object connection = connectionField.get(nmsPlayer);
Method sendPacket = ReflectionUtils.getMethod(connection.getClass(), "sendPacket");
sendPacket.invoke(connection, packet);
}
catch (Exception e) {
e.printStackTrace();
}
}
private static class FakeDragon {
private static final int MAX_HEALTH = 200;
private int id;
private int x;
private int y;
private int z;
private int pitch = 0;
private int yaw = 0;
private byte xvel = 0;
private byte yvel = 0;
private byte zvel = 0;
private float health;
private boolean visible = false;
private String name;
private Object world;
private Object dragon;
public FakeDragon(Location loc, String name, float percent) {
this.name = name;
this.x = loc.getBlockX();
this.y = loc.getBlockY();
this.z = loc.getBlockZ();
this.health = percent * MAX_HEALTH;
this.world = ReflectionUtils.getHandle(loc.getWorld());
}
public void setHealth(float percent) {
this.health = percent / MAX_HEALTH;
}
public void setName(String name) {
this.name = name;
}
public Object getSpawnPacket() {
Class<?> Entity = ReflectionUtils.getCraftClass("Entity");
Class<?> EntityLiving = ReflectionUtils.getCraftClass("EntityLiving");
Class<?> EntityEnderDragon = ReflectionUtils.getCraftClass("EntityEnderDragon");
try{
dragon = EntityEnderDragon.getConstructor(ReflectionUtils.getCraftClass("World")).newInstance(world);
ReflectionUtils.getMethod(EntityEnderDragon, "setLocation", double.class, double.class, double.class, float.class, float.class).invoke(dragon, x, y, z, pitch, yaw);
ReflectionUtils.getMethod(EntityEnderDragon, "setInvisible", boolean.class).invoke(dragon, visible);
ReflectionUtils.getMethod(EntityEnderDragon, "setCustomName", String.class ).invoke(dragon, name);
ReflectionUtils.getMethod(EntityEnderDragon, "setHealth", float.class).invoke(dragon, health);
ReflectionUtils.getField(Entity, "motX").set(dragon, xvel);
ReflectionUtils.getField(Entity, "motY").set(dragon, yvel);
ReflectionUtils.getField(Entity, "motZ").set(dragon, zvel);
this.id = (Integer) ReflectionUtils.getMethod(EntityEnderDragon, "getId").invoke(dragon);
Class<?> packetClass = ReflectionUtils.getCraftClass("PacketPlayOutSpawnEntityLiving");
return packetClass.getConstructor(new Class<?>[]{ EntityLiving }).newInstance(dragon);
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
public Object getDestroyPacket(){
try{
Class<?> packetClass = ReflectionUtils.getCraftClass("PacketPlayOutEntityDestroy");
return packetClass.getConstructor(new Class<?>[]{int[].class}).newInstance(new int[]{id});
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
public Object getMetaPacket(Object watcher){
try{
Class<?> watcherClass = ReflectionUtils.getCraftClass("DataWatcher");
Class<?> packetClass = ReflectionUtils.getCraftClass("PacketPlayOutEntityMetadata");
return packetClass.getConstructor(new Class<?>[] { int.class, watcherClass, boolean.class }).newInstance(id, watcher, true);
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
public Object getTeleportPacket(Location loc){
try{
Class<?> packetClass = ReflectionUtils.getCraftClass("PacketPlayOutEntityTeleport");
return packetClass.getConstructor(new Class<?>[] { int.class, int.class, int.class, int.class, byte.class, byte.class }).newInstance(
this.id, loc.getBlockX() * 32, loc.getBlockY() * 32, loc.getBlockZ() * 32, (byte) ((int) loc.getYaw() * 256 / 360), (byte) ((int) loc.getPitch() * 256 / 360));
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
public Object getWatcher(){
Class<?> Entity = ReflectionUtils.getCraftClass("Entity");
Class<?> DataWatcher = ReflectionUtils.getCraftClass("DataWatcher");
try{
Object watcher = DataWatcher.getConstructor(new Class<?>[] { Entity }).newInstance(dragon);
Method a = ReflectionUtils.getMethod(DataWatcher, "a", new Class<?>[] { int.class, Object.class });
a.invoke(watcher, 0, visible ? (byte) 0 : (byte) 0x20);
a.invoke(watcher, 6, (Float) health);
a.invoke(watcher, 7, (Integer) 0);
a.invoke(watcher, 8, (Byte) (byte) 0);
a.invoke(watcher, 10, name);
a.invoke(watcher, 11, (Byte) (byte) 1);
return watcher;
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
}
private static class ReflectionUtils {
public static Class<?> getCraftClass(String ClassName) {
String name = Bukkit.getServer().getClass().getPackage().getName();
String version = name.substring(name.lastIndexOf('.') + 1) + ".";
String className = "net.minecraft.server." + version + ClassName;
Class<?> c = null;
try {
c = Class.forName(className);
}
catch (Exception e) { e.printStackTrace(); }
return c;
}
public static Object getHandle(Entity entity) {
try {
return getMethod(entity.getClass(), "getHandle").invoke(entity);
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
public static Object getHandle(World world) {
try {
return getMethod(world.getClass(), "getHandle").invoke(world);
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
public static Field getField(Class<?> cl, String field_name) {
try {
return cl.getDeclaredField(field_name);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Method getMethod(Class<?> cl, String method, Class<?>... args) {
for (Method m : cl.getMethods())
if (m.getName().equals(method) && ClassListEqual(args, m.getParameterTypes()))
return m;
return null;
}
public static Method getMethod(Class<?> cl, String method) {
for (Method m : cl.getMethods())
if (m.getName().equals(method))
return m;
return null;
}
public static boolean ClassListEqual(Class<?>[] l1, Class<?>[] l2) {
boolean equal = true;
if (l1.length != l2.length)
return false;
for (int i = 0; i < l1.length; i++)
if (l1[i] != l2[i]) {
equal = false;
break;
}
return equal;
}
}
}