package com.bergerkiller.bukkit.common.bases; import java.util.List; import java.util.Random; import java.util.UUID; import net.minecraft.server.Entity; import net.minecraft.server.EntityInsentient; import net.minecraft.server.EntityPlayer; import net.minecraft.server.WatchableObject; import org.bukkit.EntityEffect; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.craftbukkit.CraftSound; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.entity.Vehicle; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; import com.bergerkiller.bukkit.common.bases.mutable.LocationAbstract; import com.bergerkiller.bukkit.common.bases.mutable.VectorAbstract; import com.bergerkiller.bukkit.common.conversion.Conversion; import com.bergerkiller.bukkit.common.internal.CommonNMS; import com.bergerkiller.bukkit.common.protocol.CommonPacket; import com.bergerkiller.bukkit.common.protocol.PacketType; import com.bergerkiller.bukkit.common.reflection.classes.DataWatcherRef; import com.bergerkiller.bukkit.common.reflection.classes.EntityRef; import com.bergerkiller.bukkit.common.utils.CommonUtil; import com.bergerkiller.bukkit.common.utils.EntityUtil; import com.bergerkiller.bukkit.common.utils.MathUtil; import com.bergerkiller.bukkit.common.utils.PacketUtil; import com.bergerkiller.bukkit.common.utils.WorldUtil; import com.bergerkiller.bukkit.common.wrappers.DataWatcher; /** * Extends the methods provided by the Entity Bukkit class. * * @param <T> - type of Entity */ public class ExtendedEntity<T extends org.bukkit.entity.Entity> { public final LocationAbstract loc = new LocationAbstract() { public World getWorld() {return ExtendedEntity.this.getWorld();} public LocationAbstract setWorld(World world) {ExtendedEntity.this.setWorld(world); return this;} public double getX() {return ExtendedEntity.this.h().locX;} public double getY() {return ExtendedEntity.this.h().locY;} public double getZ() {return ExtendedEntity.this.h().locZ;} public LocationAbstract setX(double x) {ExtendedEntity.this.h().locX = x; return this;} public LocationAbstract setY(double y) {ExtendedEntity.this.h().locY = y; return this;} public LocationAbstract setZ(double z) {ExtendedEntity.this.h().locZ = z; return this;} public float getYaw() {return ExtendedEntity.this.h().yaw;} public float getPitch() {return ExtendedEntity.this.h().pitch;} public LocationAbstract setYaw(float yaw) {ExtendedEntity.this.h().yaw = yaw; return this;} public LocationAbstract setPitch(float pitch) {ExtendedEntity.this.h().pitch = pitch; return this;} }; public final LocationAbstract last = new LocationAbstract() { public World getWorld() {return ExtendedEntity.this.getWorld();} public LocationAbstract setWorld(World world) {return this;} public double getX() {return ExtendedEntity.this.h().lastX;} public double getY() {return ExtendedEntity.this.h().lastY;} public double getZ() {return ExtendedEntity.this.h().lastZ;} public LocationAbstract setX(double x) {ExtendedEntity.this.h().lastX = x; return this;} public LocationAbstract setY(double y) {ExtendedEntity.this.h().lastY = y; return this;} public LocationAbstract setZ(double z) {ExtendedEntity.this.h().lastZ = z; return this;} public float getYaw() {return ExtendedEntity.this.h().lastYaw;} public float getPitch() {return ExtendedEntity.this.h().lastPitch;} public LocationAbstract setYaw(float yaw) {ExtendedEntity.this.h().lastYaw = yaw; return this;} public LocationAbstract setPitch(float pitch) {ExtendedEntity.this.h().lastPitch = pitch; return this;} }; public final VectorAbstract vel = new VectorAbstract() { public double getX() {return ExtendedEntity.this.h().motX;} public double getY() {return ExtendedEntity.this.h().motY;} public double getZ() {return ExtendedEntity.this.h().motZ;} public VectorAbstract setX(double x) {ExtendedEntity.this.h().motX = x; return this;} public VectorAbstract setY(double y) {ExtendedEntity.this.h().motY = y; return this;} public VectorAbstract setZ(double z) {ExtendedEntity.this.h().motZ = z; return this;} }; /** * The minimum x/y/z velocity distance, above which the entity is considered to be moving */ public static final double MIN_MOVE_SPEED = 0.001; /** * The internally-stored Bukkit Entity instance */ protected T entity; /** * Constructs a new Extended Entity with the initial entity specified * * @param entity to use */ public ExtendedEntity(T entity) { setEntity(entity); } /** * Sets the backing Bukkit Entity * * @param entity to set to */ protected void setEntity(T entity) { this.entity = entity; } /** * Gets the backing Bukkit Entity * * @return entity */ public T getEntity() { return entity; } /** * Private method for access the NMS Entity handle * * @return Entity handle */ private Entity h() { return getHandle(Entity.class); } /** * Gets the Entity handle * * @return the Entity handle */ public Object getHandle() { return Conversion.toEntityHandle.convert(entity); } /** * Gets the Entity handle, and automatically casts it to a given type * * @param type to cast to * @return the NMS entity handle, cast to the given type */ public <H> H getHandle(Class<H> type) { return CommonUtil.tryCast(getHandle(), type); } /** * Obtains the DataWatcher to update and keep track of Entity metadata * * @return Entity meta data watcher */ public DataWatcher getMetaData() { return new DataWatcher(getHandle(Entity.class).getDataWatcher()); } public int getChunkX() { return EntityRef.chunkX.get(getHandle()); } public void setChunkX(int value) { EntityRef.chunkX.set(getHandle(), value); } public int getChunkY() { return EntityRef.chunkY.get(getHandle()); } public void setChunkY(int value) { EntityRef.chunkY.set(getHandle(), value); } public int getChunkZ() { return EntityRef.chunkZ.get(getHandle()); } public void setChunkZ(int value) { EntityRef.chunkZ.set(getHandle(), value); } /** * Obtains the Entity head rotation angle, or 0.0 if this Entity has no head. * * @return Head rotation, if available */ public float getHeadRotation() { return getHandle(Entity.class).getHeadRotation(); } public double getMovedX() { return loc.getX() - last.getX(); } public double getMovedY() { return loc.getY() - last.getY(); } public double getMovedZ() { return loc.getZ() - last.getZ(); } public boolean hasMovedHorizontally() { return Math.abs(this.getMovedX()) > MIN_MOVE_SPEED || Math.abs(this.getMovedZ()) > MIN_MOVE_SPEED; } public boolean hasMovedVertically() { return Math.abs(this.getMovedY()) > MIN_MOVE_SPEED; } public boolean hasMoved() { return hasMovedHorizontally() || hasMovedVertically(); } public double getMovedXZDistance() { return MathUtil.length(getMovedX(), getMovedZ()); } public double getMovedXZDistanceSquared() { return MathUtil.lengthSquared(getMovedX(), getMovedZ()); } public double getMovedDistance() { return MathUtil.length(getMovedX(), getMovedY(), getMovedZ()); } public double getMovedDistanceSquared() { return MathUtil.lengthSquared(getMovedX(), getMovedY(), getMovedZ()); } public boolean isMovingHorizontally() { return vel.x.abs() > MIN_MOVE_SPEED || vel.z.abs() > MIN_MOVE_SPEED; } public boolean isMovingVertically() { return vel.y.abs() > MIN_MOVE_SPEED; } public boolean isMoving() { return isMovingHorizontally() || isMovingVertically(); } public void setWorld(World world) { final Entity handle = getHandle(Entity.class); handle.world = CommonNMS.getNative(world); handle.dimension = WorldUtil.getDimension(world); } public void setDead(boolean dead) { getHandle(Entity.class).dead = dead; } public float getHeight() { return getHandle(Entity.class).height; } public void setHeight(float height) { getHandle(Entity.class).height = height; } public float getLength() { return getHandle(Entity.class).length; } public void setLength(float length) { getHandle(Entity.class).length = length; } public boolean isOnGround() { return getHandle(Entity.class).onGround; } public void setOnGround(boolean onGround) { getHandle(Entity.class).onGround = onGround; } public boolean isPositionChanged() { return EntityRef.positionChanged.get(getHandle()); } public void setPositionChanged(boolean changed) { EntityRef.positionChanged.set(getHandle(), changed); } public boolean isVelocityChanged() { return EntityRef.velocityChanged.get(getHandle()); } public void setVelocityChanged(boolean changed) { EntityRef.velocityChanged.set(getHandle(), changed); } /** * Gets whether this Entity is stored in a loaded chunk * * @return True if loaded, False if not */ public boolean isInLoadedChunk() { return EntityRef.isLoaded.get(getHandle()); } /** * Gets whether the entity is hitting something, like an Entity or Block. * If this returns True, then the Entity is unable to move freely. * * @return True if movement is impaired, False if not */ public boolean isMovementImpaired() { // Note: this variable is simply wrongly deobfuscated! return getHandle(Entity.class).positionChanged; } /** * Sets whether the entity is hitting something, and as a result can not move freely * * @param impaired state to set to */ public void setMovementImpaired(boolean impaired) { // Note: this variable is simply wrongly deobfuscated! getHandle(Entity.class).positionChanged = impaired; } public Random getRandom() { return EntityRef.random.get(getHandle()); } /** * Plays a sound for the Entity, but with a slightly random pitch * * @param sound to play * @param volume to play at * @param pitch (average) to play at */ public void makeRandomSound(Sound sound, float volume, float pitch) { makeRandomSound(CraftSound.getSound(sound), volume, pitch); } /** * Plays a sound for the Entity, but with a slightly random pitch * * @param soundName to play * @param volume to play at * @param pitch (average) to play at */ public void makeRandomSound(String soundName, float volume, float pitch) { final Random rand = getRandom(); makeSound(soundName, volume, MathUtil.clamp(pitch + 0.4f * (rand.nextFloat() - rand.nextFloat()), 0.0f, 1.0f)); } public void makeSound(Sound sound, float volume, float pitch) { makeSound(CraftSound.getSound(sound), volume, pitch); } public void makeSound(String soundName, float volume, float pitch) { final Entity handle = getHandle(Entity.class); handle.world.makeSound(handle, soundName, volume, pitch); } public void makeStepSound(org.bukkit.block.Block block) { makeStepSound(block.getX(), block.getY(), block.getZ(), block.getType()); } public void makeStepSound(int blockX, int blockY, int blockZ, Material type) { EntityRef.playStepSound(getHandle(), blockX, blockY, blockZ, CommonNMS.getBlock(type)); } @Deprecated public void makeStepSound(int blockX, int blockY, int blockZ, int typeId) { if (CommonNMS.isValidBlockId(typeId)) { EntityRef.playStepSound(getHandle(), blockX, blockY, blockZ, typeId); } } public List<MetadataValue> getMetadata(String arg0) { return entity.getMetadata(arg0); } public boolean hasMetadata(String arg0) { return entity.hasMetadata(arg0); } public void removeMetadata(String arg0, Plugin arg1) { entity.removeMetadata(arg0, arg1); } public void setMetadata(String arg0, MetadataValue arg1) { entity.setMetadata(arg0, arg1); } public boolean eject() { return entity.eject(); } public int getEntityId() { return entity.getEntityId(); } public float getFallDistance() { return entity.getFallDistance(); } public int getFireTicks() { return entity.getFireTicks(); } public EntityDamageEvent getLastDamageCause() { return entity.getLastDamageCause(); } public Location getLocation() { return entity.getLocation(); } public Location getLastLocation() { return last.toLocation(); } public Location getLocation(Location arg0) { return entity.getLocation(arg0); } public int getMaxFireTicks() { return entity.getMaxFireTicks(); } public void setFootLocation(double x, double y, double z, float yaw, float pitch) { getHandle(Entity.class).setPositionRotation(x, y, z, yaw, pitch); } public void setLocation(double x, double y, double z, float yaw, float pitch) { getHandle(Entity.class).setLocation(x, y, z, yaw, pitch); } /** * Sets the yaw and pitch rotation, while ensuring that there are no 360-turns * * @param yaw to set to * @param pitch to set to */ public void setRotation(float yaw, float pitch) { EntityRef.setRotation(getHandle(), yaw, pitch); } public void setPosition(double x, double y, double z) { getHandle(Entity.class).setPosition(x, y, z); } public List<org.bukkit.entity.Entity> getNearbyEntities(double radius) { return this.getNearbyEntities(radius, radius, radius); } public List<org.bukkit.entity.Entity> getNearbyEntities(double radX, double radY, double radZ) { return WorldUtil.getNearbyEntities(this.getEntity(), radX, radY, radZ); } public org.bukkit.entity.Entity getPassenger() { return entity.getPassenger(); } public Player getPlayerPassenger() { return CommonUtil.tryCast(getPassenger(), Player.class); } public boolean hasPassenger() { return getHandle(Entity.class).passenger != null; } public boolean hasPlayerPassenger() { return getHandle(Entity.class).passenger instanceof EntityPlayer; } /** * Gets whether this type of Entity is a type of Vehicle, allowing entities to enter it * * @return True if this is a Vehicle, False if not */ public boolean isVehicle() { return entity instanceof Vehicle; } public Server getServer() { return entity.getServer(); } public int getTicksLived() { return entity.getTicksLived(); } public EntityType getType() { return entity.getType(); } public UUID getUniqueId() { return entity.getUniqueId(); } public org.bukkit.entity.Entity getVehicle() { return entity.getVehicle(); } public Vector getVelocity() { return entity.getVelocity(); } /** * Checks whether this Entity is spawned in the world * * @return True if the Entity is spawned, False if not */ public boolean isSpawned() { final Entity handle = h(); return handle != null && handle.world != null && handle.world.entityList.contains(handle); } public World getWorld() { return Conversion.toWorld.convert(h().world); } public boolean isDead() { return entity.isDead(); } public boolean isEmpty() { return entity.isEmpty(); } public boolean isInsideVehicle() { return entity.isInsideVehicle(); } /** * Gets the Entity that is holding this Entity by a leash. * If this Entity does not support leashing, or the Entity is * not on a leash, null is returned instead. * * @return Leash holder */ public org.bukkit.entity.Entity getLeashHolder() { EntityInsentient handle = getHandle(EntityInsentient.class); return handle == null ? null : Conversion.toEntity.convert(handle.getLeashHolder()); } public boolean isValid() { return entity.isValid(); } /** * Gets whether the Entity is submerged in water (subsequently, extinguising any fire). * This method does not update the state, it merely reads it. * * @return True if this Entity is in water, False if not */ public boolean isInWater() { return isInWater(false); } /** * Gets whether the Entity is submerged in water (subsequently, extinguising any fire) * * @param update option: True to update this state by checking for water blocks nearby * @return True if this Entity is in water, False if not */ public boolean isInWater(boolean update) { return EntityRef.isInWater(getHandle(), update); } /** * Sets whether an Entity is allowed to teleport upon entering a portal right now. * This state is live-updated based on whether the Entity moved into/away from a portal. * * @param allow whether to allow portal entering */ public void setAllowTeleportation(boolean allow) { EntityUtil.setAllowTeleportation(entity, allow); } /** * Gets whether an Entity is allowed to teleport upon entering a portal right now. * This state is live-updated based on whether the Entity moved into/away from a portal. */ public boolean getAllowTeleportation() { return EntityUtil.getAllowTeleportation(entity); } /** * Gets the entity portal enter cooldown ticks * * @return entity cooldown ticks */ public int getPortalCooldown() { return EntityUtil.getPortalCooldown(entity); } /** * Sets the entity portal enter cooldown ticks * * @param cooldownTicks to set to */ public void setPortalCooldown(int cooldownTicks) { EntityUtil.setPortalCooldown(entity, cooldownTicks); } /** * Gets the maximum portal cooldown ticks. * This is the value applied right after entering a portal. * * @return entity maximum portal cooldown ticks */ public int getPortalCooldownMaximum() { return EntityUtil.getPortalCooldownMaximum(entity); } public boolean leaveVehicle() { return entity.leaveVehicle(); } public void playEffect(EntityEffect arg0) { entity.playEffect(arg0); } public void remove() { entity.remove(); } public void setFallDistance(float arg0) { entity.setFallDistance(arg0); } public void setFireTicks(int arg0) { entity.setFireTicks(arg0); } public void setLastDamageCause(EntityDamageEvent arg0) { entity.setLastDamageCause(arg0); } /** * Sets the passenger of this Vehicle, while throwing possible events * If the previous passenger could not eject, or if entering didn't happen, False is returned. * * @param passenger to set to * @return True if the passenger was successfully set, False if not */ public boolean setPassenger(org.bukkit.entity.Entity passenger) { return passenger == null ? entity.eject() : entity.setPassenger(passenger); } /** * Sets the passenger of this Vehicle without raising any events * * @param newPassenger to set to */ public void setPassengerSilent(org.bukkit.entity.Entity newPassenger) { final Entity handle = getHandle(Entity.class); if (hasPassenger()) { if (getPassenger() == newPassenger) { // Ignore setting to the same Entity return; } // Send proper eject packet for the previous passenger if (hasPlayerPassenger()) { PacketUtil.sendPacket(getPlayerPassenger(), PacketType.OUT_ENTITY_ATTACH.newInstance(getPassenger(), null)); } // Properly set to null handle.passenger.vehicle = null; handle.passenger = null; } // Set the new passenger if (newPassenger != null) { // Properly set it handle.passenger = CommonNMS.getNative(newPassenger); handle.passenger.vehicle = handle; // Send proper eject packet for the new passenger if (hasPlayerPassenger()) { PacketUtil.sendPacket(getPlayerPassenger(), PacketType.OUT_ENTITY_ATTACH.newInstance(newPassenger, this.getEntity())); } } } /** * Sends a packet to all nearby players in view of this Entity * * @param packet to send */ public void sendPacketNearby(CommonPacket packet) { WorldUtil.getTracker(getWorld()).sendPacket(entity, packet); } public void setTicksLived(int arg0) { entity.setTicksLived(arg0); } public void setVelocity(Vector arg0) { entity.setVelocity(arg0); } public void setVelocity(double motX, double motY, double motZ) { final Entity handle = getHandle(Entity.class); handle.motX = motX; handle.motY = motY; handle.motZ = motZ; } public boolean teleport(Location location) { return teleport(location, TeleportCause.PLUGIN); } public boolean teleport(Location location, TeleportCause cause) { return entity.teleport(location, cause); } public boolean teleport(org.bukkit.entity.Entity destination) { return teleport(destination.getLocation()); } public boolean teleport(org.bukkit.entity.Entity destination, TeleportCause cause) { return teleport(destination.getLocation(), cause); } /** * Spawns an item as if dropped by this Entity * * @param material of the item to drop * @param amount of the material to drop * @param force to drop at * @return the dropped Item */ public Item spawnItemDrop(Material material, int amount, float force) { return CommonNMS.getItem(getHandle(Entity.class).a(CraftMagicNumbers.getItem(material), amount, force)); } /** * Spawns an item as if dropped by this Entity * * @param item to drop * @param force to drop at * @return the dropped Item */ public Item spawnItemDrop(org.bukkit.inventory.ItemStack item, float force) { return CommonNMS.getItem(getHandle(Entity.class).a(CommonNMS.getNative(item), force)); } /** * Sets a DataWatcher value at the specified index * * @param index to set at * @param value to set to */ public void setWatchedData(int index, Object value) { h().getDataWatcher().watch(index, value); } /** * Gets a DataWatcher value at the specified index * * @param index to get at * @param def to return on failure and type to get (can not be null) * @return data, or def if not found */ @SuppressWarnings("unchecked") public <K> K getWatchedData(int index, K def) { return getWatchedData(index, (Class<K>) def.getClass(), def); } /** * Gets a DataWatcher value at the specified index * * @param index to get at * @param type of data to get * @param def to return on failure * @return data, or def if not found */ public <K> K getWatchedData(int index, Class<K> type, K def) { WatchableObject object = (WatchableObject) DataWatcherRef.read.invoke(h().getDataWatcher(), index); if (object == null) { return def; } return Conversion.convert(object.b(), type, def); } @Override public int hashCode() { return entity == null ? super.hashCode() : entity.hashCode(); } @Override public String toString() { return entity == null ? "null" : entity.toString(); } @Override public boolean equals(Object object) { if (object instanceof ExtendedEntity) { return ((ExtendedEntity<?>) object).entity == this.entity; } else { return false; } } }