381 lines
14 KiB
Java
381 lines
14 KiB
Java
package net.citizensnpcs.util;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Field;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.WeakHashMap;
|
|
|
|
import net.citizensnpcs.api.npc.NPC;
|
|
import net.minecraft.server.v1_4_5.ControllerJump;
|
|
import net.minecraft.server.v1_4_5.ControllerLook;
|
|
import net.minecraft.server.v1_4_5.DamageSource;
|
|
import net.minecraft.server.v1_4_5.EnchantmentManager;
|
|
import net.minecraft.server.v1_4_5.Entity;
|
|
import net.minecraft.server.v1_4_5.EntityLiving;
|
|
import net.minecraft.server.v1_4_5.EntityMonster;
|
|
import net.minecraft.server.v1_4_5.EntityTypes;
|
|
import net.minecraft.server.v1_4_5.MathHelper;
|
|
import net.minecraft.server.v1_4_5.MobEffectList;
|
|
import net.minecraft.server.v1_4_5.Navigation;
|
|
import net.minecraft.server.v1_4_5.NetworkManager;
|
|
import net.minecraft.server.v1_4_5.Packet;
|
|
import net.minecraft.server.v1_4_5.PathfinderGoalSelector;
|
|
import net.minecraft.server.v1_4_5.World;
|
|
|
|
import org.apache.commons.lang.Validate;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
|
|
import org.bukkit.craftbukkit.v1_4_5.CraftWorld;
|
|
import org.bukkit.craftbukkit.v1_4_5.entity.CraftEntity;
|
|
import org.bukkit.craftbukkit.v1_4_5.entity.CraftLivingEntity;
|
|
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer;
|
|
import org.bukkit.entity.EntityType;
|
|
import org.bukkit.entity.LivingEntity;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.material.Stairs;
|
|
import org.bukkit.material.Step;
|
|
import org.bukkit.plugin.PluginLoadOrder;
|
|
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Sets;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public class NMS {
|
|
private NMS() {
|
|
// util class
|
|
}
|
|
|
|
public static void sendToOnline(Packet... packets) {
|
|
Validate.notNull(packets, "packets cannot be null");
|
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
if (player == null || !player.isOnline())
|
|
continue;
|
|
for (Packet packet : packets) {
|
|
sendPacket(player, packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void sendPacketNearby(Location location, Packet packet, double radius) {
|
|
radius *= radius;
|
|
final org.bukkit.World world = location.getWorld();
|
|
for (Player ply : Bukkit.getServer().getOnlinePlayers()) {
|
|
if (ply == null || world != ply.getWorld()) {
|
|
continue;
|
|
}
|
|
if (location.distanceSquared(ply.getLocation()) > radius) {
|
|
continue;
|
|
}
|
|
sendPacket(ply, packet);
|
|
}
|
|
}
|
|
|
|
public static void sendPacketNearby(Location location, Packet packet) {
|
|
NMS.sendPacketNearby(location, packet, 64);
|
|
}
|
|
|
|
private static final float DEFAULT_SPEED = 0.4F;
|
|
private static Map<Class<?>, Integer> ENTITY_CLASS_TO_INT;
|
|
private static final Map<Class<?>, Constructor<?>> ENTITY_CONSTRUCTOR_CACHE = new WeakHashMap<Class<?>, Constructor<?>>();
|
|
private static Map<Integer, Class<?>> ENTITY_INT_TO_CLASS;
|
|
private static Field GOAL_FIELD;
|
|
private static Field LAND_SPEED_MODIFIER_FIELD;
|
|
private static final Map<EntityType, Float> MOVEMENT_SPEEDS = Maps.newEnumMap(EntityType.class);
|
|
private static Field NAVIGATION_WORLD_FIELD;
|
|
private static Field PATHFINDING_RANGE;
|
|
private static final Random RANDOM = Util.getFastRandom();
|
|
private static Set<Integer> SLAB_MATERIALS = Sets.newHashSet();
|
|
private static Field SPEED_FIELD;
|
|
private static Set<Integer> STAIR_MATERIALS = Sets.newHashSet();
|
|
|
|
private static Field THREAD_STOPPER;
|
|
|
|
public static void addOrRemoveFromPlayerList(LivingEntity bukkitEntity, boolean remove) {
|
|
if (bukkitEntity == null)
|
|
return;
|
|
EntityLiving handle = getHandle(bukkitEntity);
|
|
if (handle.world == null)
|
|
return;
|
|
if (remove) {
|
|
handle.world.players.remove(handle);
|
|
} else if (!handle.world.players.contains(handle)) {
|
|
handle.world.players.add(handle);
|
|
}
|
|
}
|
|
|
|
public static void attack(EntityLiving handle, EntityLiving target) {
|
|
int damage = handle instanceof EntityMonster ? ((EntityMonster) handle).c((Entity) target) : 2;
|
|
|
|
if (handle.hasEffect(MobEffectList.INCREASE_DAMAGE)) {
|
|
damage += 3 << handle.getEffect(MobEffectList.INCREASE_DAMAGE).getAmplifier();
|
|
}
|
|
|
|
if (handle.hasEffect(MobEffectList.WEAKNESS)) {
|
|
damage -= 2 << handle.getEffect(MobEffectList.WEAKNESS).getAmplifier();
|
|
}
|
|
|
|
int knockbackLevel = 0;
|
|
|
|
if (target instanceof EntityLiving) {
|
|
damage += EnchantmentManager.a(handle, target);
|
|
knockbackLevel += EnchantmentManager.getKnockbackEnchantmentLevel(handle, target);
|
|
}
|
|
|
|
boolean success = target.damageEntity(DamageSource.mobAttack(handle), damage);
|
|
|
|
if (!success)
|
|
return;
|
|
if (knockbackLevel > 0) {
|
|
target.g(-MathHelper.sin((float) (handle.yaw * Math.PI / 180.0F)) * knockbackLevel * 0.5F, 0.1D,
|
|
|
|
MathHelper.cos((float) (handle.yaw * Math.PI / 180.0F)) * knockbackLevel * 0.5F);
|
|
handle.motX *= 0.6D;
|
|
handle.motZ *= 0.6D;
|
|
}
|
|
|
|
int fireAspectLevel = EnchantmentManager.getFireAspectEnchantmentLevel(handle);
|
|
|
|
if (fireAspectLevel > 0)
|
|
target.setOnFire(fireAspectLevel * 4);
|
|
}
|
|
|
|
public static void clearGoals(PathfinderGoalSelector... goalSelectors) {
|
|
if (GOAL_FIELD == null || goalSelectors == null)
|
|
return;
|
|
for (PathfinderGoalSelector selector : goalSelectors) {
|
|
try {
|
|
List<?> list = (List<?>) NMS.GOAL_FIELD.get(selector);
|
|
list.clear();
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_CLEARING_GOALS, e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Constructor<?> getCustomEntityConstructor(Class<?> clazz, EntityType type)
|
|
throws SecurityException, NoSuchMethodException {
|
|
Constructor<?> constructor = ENTITY_CONSTRUCTOR_CACHE.get(clazz);
|
|
if (constructor == null) {
|
|
constructor = clazz.getConstructor(World.class);
|
|
constructor.setAccessible(true);
|
|
ENTITY_CLASS_TO_INT.put(clazz, (int) type.getTypeId());
|
|
ENTITY_CONSTRUCTOR_CACHE.put(clazz, constructor);
|
|
}
|
|
return constructor;
|
|
}
|
|
|
|
private static Field getField(Class<?> clazz, String field) {
|
|
Field f = null;
|
|
try {
|
|
f = clazz.getDeclaredField(field);
|
|
f.setAccessible(true);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_GETTING_FIELD, field, e.getMessage());
|
|
}
|
|
return f;
|
|
}
|
|
|
|
public static EntityLiving getHandle(LivingEntity entity) {
|
|
return ((CraftLivingEntity) entity).getHandle();
|
|
}
|
|
|
|
public static float getSpeedFor(NPC npc) {
|
|
EntityType entityType = npc.getBukkitEntity().getType();
|
|
Float cached = MOVEMENT_SPEEDS.get(entityType);
|
|
if (cached != null)
|
|
return cached;
|
|
if (SPEED_FIELD == null) {
|
|
MOVEMENT_SPEEDS.put(entityType, DEFAULT_SPEED);
|
|
return DEFAULT_SPEED;
|
|
}
|
|
try {
|
|
float speed = SPEED_FIELD.getFloat(((CraftEntity) npc.getBukkitEntity()).getHandle());
|
|
MOVEMENT_SPEEDS.put(entityType, speed);
|
|
return speed;
|
|
} catch (IllegalAccessException ex) {
|
|
ex.printStackTrace();
|
|
return DEFAULT_SPEED;
|
|
}
|
|
}
|
|
|
|
public static boolean inWater(LivingEntity entity) {
|
|
EntityLiving mcEntity = getHandle(entity);
|
|
return mcEntity.I() || mcEntity.J();
|
|
}
|
|
|
|
public static void loadPlugins() {
|
|
((CraftServer) Bukkit.getServer()).enablePlugins(PluginLoadOrder.POSTWORLD);
|
|
}
|
|
|
|
public static void look(ControllerLook controllerLook, EntityLiving handle, EntityLiving target) {
|
|
controllerLook.a(target, 10.0F, handle.bp());
|
|
}
|
|
|
|
public static void look(LivingEntity bukkitEntity, float yaw, float pitch) {
|
|
EntityLiving handle = getHandle(bukkitEntity);
|
|
handle.yaw = handle.ay = yaw;
|
|
handle.pitch = pitch;
|
|
}
|
|
|
|
public static void registerEntityClass(Class<?> clazz) {
|
|
if (ENTITY_CLASS_TO_INT.containsKey(clazz))
|
|
return;
|
|
Class<?> search = clazz;
|
|
while ((search = search.getSuperclass()) != null && Entity.class.isAssignableFrom(search)) {
|
|
if (!ENTITY_CLASS_TO_INT.containsKey(search))
|
|
continue;
|
|
int code = ENTITY_CLASS_TO_INT.get(search);
|
|
ENTITY_INT_TO_CLASS.put(code, clazz);
|
|
ENTITY_CLASS_TO_INT.put(clazz, code);
|
|
return;
|
|
}
|
|
throw new IllegalArgumentException("unable to find valid entity superclass");
|
|
}
|
|
|
|
public static void sendPacket(Player player, Packet packet) {
|
|
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(packet);
|
|
}
|
|
|
|
public static void setDestination(LivingEntity bukkitEntity, double x, double y, double z, float speed) {
|
|
((CraftLivingEntity) bukkitEntity).getHandle().getControllerMove().a(x, y, z, speed);
|
|
}
|
|
|
|
public static void setHeadYaw(EntityLiving handle, float yaw) {
|
|
handle.ay = yaw;
|
|
}
|
|
|
|
public static void setLandSpeedModifier(EntityLiving handle, float speed) {
|
|
if (LAND_SPEED_MODIFIER_FIELD == null)
|
|
return;
|
|
try {
|
|
LAND_SPEED_MODIFIER_FIELD.setFloat(handle, speed);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_UPDATING_SPEED, e.getMessage());
|
|
}
|
|
}
|
|
|
|
public static void setShouldJump(LivingEntity entity) {
|
|
ControllerJump controller = getHandle(entity).getControllerJump();
|
|
controller.a();
|
|
}
|
|
|
|
public static org.bukkit.entity.Entity spawnCustomEntity(org.bukkit.World world, Location at,
|
|
Class<? extends Entity> clazz, EntityType type) {
|
|
World handle = ((CraftWorld) world).getHandle();
|
|
Entity entity = null;
|
|
try {
|
|
Constructor<?> constructor = getCustomEntityConstructor(clazz, type);
|
|
entity = (Entity) constructor.newInstance(handle);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_SPAWNING_CUSTOM_ENTITY, e.getMessage());
|
|
return null;
|
|
}
|
|
handle.addEntity(entity);
|
|
entity.setLocation(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
|
|
return entity.getBukkitEntity();
|
|
}
|
|
|
|
public static void stopNetworkThreads(NetworkManager manager) {
|
|
if (THREAD_STOPPER == null)
|
|
return;
|
|
try {
|
|
THREAD_STOPPER.set(manager, false);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_STOPPING_NETWORK_THREADS, e.getMessage());
|
|
}
|
|
}
|
|
|
|
public static void trySwim(LivingEntity handle) {
|
|
trySwim(handle, 0.04F);
|
|
}
|
|
|
|
public static void trySwim(LivingEntity entity, float power) {
|
|
Entity handle = getHandle(entity);
|
|
if (RANDOM.nextFloat() < 0.8F && inWater(entity)) {
|
|
handle.motY += power;
|
|
}
|
|
}
|
|
|
|
public static void updateAI(EntityLiving entity) {
|
|
updateSenses(entity);
|
|
entity.getNavigation().e();
|
|
entity.getControllerMove().c();
|
|
entity.getControllerLook().a();
|
|
entity.getControllerJump().b();
|
|
}
|
|
|
|
public static void updateNavigationWorld(LivingEntity entity, org.bukkit.World world) {
|
|
if (NAVIGATION_WORLD_FIELD == null)
|
|
return;
|
|
EntityLiving handle = ((CraftLivingEntity) entity).getHandle();
|
|
World worldHandle = ((CraftWorld) world).getHandle();
|
|
try {
|
|
NAVIGATION_WORLD_FIELD.set(handle.getNavigation(), worldHandle);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_UPDATING_NAVIGATION_WORLD, e.getMessage());
|
|
}
|
|
}
|
|
|
|
public static void updatePathfindingRange(NPC npc, float pathfindingRange) {
|
|
if (PATHFINDING_RANGE == null)
|
|
return;
|
|
Navigation navigation = ((CraftLivingEntity) npc.getBukkitEntity()).getHandle().getNavigation();
|
|
try {
|
|
PATHFINDING_RANGE.set(navigation, pathfindingRange);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_UPDATING_PATHFINDING_RANGE, e.getMessage());
|
|
}
|
|
}
|
|
|
|
public static void updateSenses(EntityLiving entity) {
|
|
entity.aA().a();
|
|
}
|
|
|
|
static {
|
|
// true field above false and three synchronised lists
|
|
THREAD_STOPPER = getField(NetworkManager.class, "m");
|
|
|
|
// constants taken from source code
|
|
MOVEMENT_SPEEDS.put(EntityType.CHICKEN, 0.25F);
|
|
MOVEMENT_SPEEDS.put(EntityType.COW, 0.2F);
|
|
MOVEMENT_SPEEDS.put(EntityType.CREEPER, 0.3F);
|
|
MOVEMENT_SPEEDS.put(EntityType.IRON_GOLEM, 0.15F);
|
|
MOVEMENT_SPEEDS.put(EntityType.MUSHROOM_COW, 0.2F);
|
|
MOVEMENT_SPEEDS.put(EntityType.OCELOT, 0.23F);
|
|
MOVEMENT_SPEEDS.put(EntityType.SHEEP, 0.25F);
|
|
MOVEMENT_SPEEDS.put(EntityType.SNOWMAN, 0.25F);
|
|
MOVEMENT_SPEEDS.put(EntityType.PIG, 0.27F);
|
|
MOVEMENT_SPEEDS.put(EntityType.PLAYER, 1F);
|
|
MOVEMENT_SPEEDS.put(EntityType.VILLAGER, 0.3F);
|
|
|
|
LAND_SPEED_MODIFIER_FIELD = getField(EntityLiving.class, "bO");
|
|
SPEED_FIELD = getField(EntityLiving.class, "bG");
|
|
NAVIGATION_WORLD_FIELD = getField(Navigation.class, "b");
|
|
PATHFINDING_RANGE = getField(Navigation.class, "e");
|
|
GOAL_FIELD = getField(PathfinderGoalSelector.class, "a");
|
|
|
|
try {
|
|
Field field = getField(EntityTypes.class, "d");
|
|
ENTITY_INT_TO_CLASS = (Map<Integer, Class<?>>) field.get(null);
|
|
field = getField(EntityTypes.class, "e");
|
|
ENTITY_CLASS_TO_INT = (Map<Class<?>, Integer>) field.get(null);
|
|
} catch (Exception e) {
|
|
Messaging.logTr(Messages.ERROR_GETTING_ID_MAPPING, e.getMessage());
|
|
}
|
|
}
|
|
|
|
static {
|
|
for (Material material : Material.values()) {
|
|
if (Step.class.isAssignableFrom(material.getData()))
|
|
SLAB_MATERIALS.add(material.getId());
|
|
else if (Stairs.class.isAssignableFrom(material.getData()))
|
|
STAIR_MATERIALS.add(material.getId());
|
|
}
|
|
}
|
|
}
|