package tc.oc.commons.bukkit.util; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import net.md_5.bungee.api.chat.BaseComponent; import net.minecraft.server.Block; import net.minecraft.server.BlockPosition; import net.minecraft.server.DataWatcher; import net.minecraft.server.Enchantment; import net.minecraft.server.EntityArmorStand; import net.minecraft.server.EntityChicken; import net.minecraft.server.EntityFireworks; import net.minecraft.server.EntityItem; import net.minecraft.server.EntityLiving; import net.minecraft.server.EntityPlayer; import net.minecraft.server.EntityTrackerEntry; import net.minecraft.server.EntityTypes; import net.minecraft.server.EntityZombie; import net.minecraft.server.EnumGamemode; import net.minecraft.server.EnumHand; import net.minecraft.server.EnumItemSlot; import net.minecraft.server.EnumParticle; import net.minecraft.server.IBlockData; import net.minecraft.server.Item; import net.minecraft.server.ItemStack; import net.minecraft.server.Items; import net.minecraft.server.MinecraftKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.MobEffect; import net.minecraft.server.MobEffectList; import net.minecraft.server.NBTTagCompound; import net.minecraft.server.Packet; import net.minecraft.server.PacketPlayInUseEntity; import net.minecraft.server.PacketPlayOutEntity; import net.minecraft.server.PacketPlayOutEntityDestroy; import net.minecraft.server.PacketPlayOutEntityEffect; import net.minecraft.server.PacketPlayOutEntityEquipment; import net.minecraft.server.PacketPlayOutEntityMetadata; import net.minecraft.server.PacketPlayOutEntityTeleport; import net.minecraft.server.PacketPlayOutEntityVelocity; import net.minecraft.server.PacketPlayOutMount; import net.minecraft.server.PacketPlayOutNamedEntitySpawn; import net.minecraft.server.PacketPlayOutPlayerInfo; import net.minecraft.server.PacketPlayOutRemoveEntityEffect; import net.minecraft.server.PacketPlayOutScoreboardTeam; import net.minecraft.server.PacketPlayOutSpawnEntity; import net.minecraft.server.PacketPlayOutSpawnEntityLiving; import net.minecraft.server.PacketPlayOutWorldBorder; import net.minecraft.server.PacketPlayOutWorldParticles; import net.minecraft.server.PlayerInteractManager; import net.minecraft.server.SoundCategory; import net.minecraft.server.SoundEffect; import net.minecraft.server.TileEntitySkull; import net.minecraft.server.WorldBorder; import net.minecraft.server.WorldServer; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Skin; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.Skull; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftSound; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftArmorStand; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftFirework; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.potion.CraftPotionEffectType; import org.bukkit.craftbukkit.registry.CraftKey; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.Skins; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.material.MaterialData; import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectTypeWrapper; import org.bukkit.registry.Key; import org.bukkit.util.Vector; import java.time.Duration; import tc.oc.commons.core.util.TimeUtils; import static com.google.common.base.Preconditions.checkArgument; public class NMSHacks { private static final Random random = new Random(); // These entity type IDs are hard-coded in a huge conditional statement in EntityTrackerEntry. // There is no nice way to get at them. private static final Map<Class<? extends Entity>, Integer> ENTITY_TYPE_IDS = ImmutableMap.of( org.bukkit.entity.Item.class, 2, ArmorStand.class, 78 ); private static EntityTrackerEntry getTrackerEntry(net.minecraft.server.Entity nms) { return ((WorldServer) nms.getWorld()).getTracker().trackedEntities.get(nms.getId()); } private static EntityTrackerEntry getTrackerEntry(Entity entity) { return getTrackerEntry(((CraftEntity) entity).getHandle()); } public static void sendPacket(Player bukkitPlayer, Object packet) { if (bukkitPlayer.isOnline()) { EntityPlayer nmsPlayer = ((CraftPlayer) bukkitPlayer).getHandle(); nmsPlayer.playerConnection.sendPacket((Packet) packet); } } private static void sendPacketToViewers(Entity entity, Object packet) { EntityTrackerEntry entry = getTrackerEntry(entity); for(EntityPlayer viewer : entry.trackedPlayers) { viewer.playerConnection.sendPacket((Packet) packet); } } public static PacketPlayOutPlayerInfo.PlayerInfoData playerListPacketData(PacketPlayOutPlayerInfo packet, UUID uuid, String name, @Nullable BaseComponent displayName, GameMode gamemode, int ping, @Nullable Skin skin) { GameProfile profile = new GameProfile(uuid, name); if(skin != null) { for(Map.Entry<String, Collection<Property>> entry : Skins.toProperties(skin).asMap().entrySet()) { profile.getProperties().putAll(entry.getKey(), entry.getValue()); } } PacketPlayOutPlayerInfo.PlayerInfoData data = packet.new PlayerInfoData(profile, ping, gamemode == null ? null : EnumGamemode.getById(gamemode.getValue()), null); data.displayName = displayName == null ? null : new BaseComponent[]{ displayName }; return data; } public static PacketPlayOutPlayerInfo.PlayerInfoData playerListPacketData(PacketPlayOutPlayerInfo packet, UUID uuid, BaseComponent displayName) { return playerListPacketData(packet, uuid, null, displayName, null, 0, null); } public static PacketPlayOutPlayerInfo.PlayerInfoData playerListPacketData(PacketPlayOutPlayerInfo packet, UUID uuid) { return playerListPacketData(packet, uuid, null, null, null, 0, null); } public static Packet playerListAddPacket(UUID uuid, String name, @Nullable BaseComponent displayName, GameMode gamemode, int ping, @Nullable Skin skin) { PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER); packet.add(playerListPacketData(packet, uuid, name, displayName, gamemode, ping, skin)); return packet; } public static Packet teamPacket(int operation, String name, String displayName, String prefix, String suffix, boolean friendlyFire, boolean seeFriendlyInvisibles, String nameTagVisibility, String collisionRule, Collection<String> players) { int flags = 0; if(friendlyFire) { flags |= 1; } if(seeFriendlyInvisibles) { flags |= 2; } return new PacketPlayOutScoreboardTeam(operation, name, displayName, prefix, suffix, 0, // color nameTagVisibility, collisionRule, flags, players); } public static Packet teamCreatePacket(String name, String displayName, String prefix, String suffix, boolean friendlyFire, boolean seeFriendlyInvisibles, String nameTagVisibility, String collisionRule, Collection<String> players) { return teamPacket(0, name, displayName, prefix, suffix, friendlyFire, seeFriendlyInvisibles, nameTagVisibility, collisionRule, players); } public static Packet teamCreatePacket(String name, String displayName, String prefix, String suffix, boolean friendlyFire, boolean seeFriendlyInvisibles, Collection<String> players) { return teamCreatePacket(name, displayName, prefix, suffix, friendlyFire, seeFriendlyInvisibles, "always", "always", players); } public static Packet teamRemovePacket(String name) { return teamPacket(1, name, null, null, null, false, false, null, null, Lists.<String>newArrayList()); } public static Packet teamJoinPacket(String name, Collection<String> players) { return teamPacket(3, name, null, null, null, false, false, null, null, players); } public static Packet teamLeavePacket(String name, Collection<String> players) { return teamPacket(4, name, null, null, null, false, false, null, null, players); } private static List<DataWatcher.Item<?>> copyEntityMetadata(Entity entity) { final List<DataWatcher.Item<?>> metadata = ((CraftEntity) entity).getHandle().getDataWatcher().c(); DataWatcher.deepCopy(metadata); return metadata; } private static byte encodeAngle(float angle) { return (byte) (angle * 256f / 360f); } private static int encodeVelocity(double v) { return (int) (v * 8000D); } private static long encodePosition(double d) { return (long) (d * 4096D); } public static Packet destroyEntitiesPacket(int... entityIds) { return new PacketPlayOutEntityDestroy(entityIds); } public static Packet spawnEntityPacket(Class<? extends Entity> type, int data, int entityId, UUID uuid, Location location, Vector velocity) { checkArgument(ENTITY_TYPE_IDS.containsKey(type)); return new PacketPlayOutSpawnEntity(entityId, uuid, location.getX(), location.getY(), location.getZ(), encodeVelocity(velocity.getX()), encodeVelocity(velocity.getY()), encodeVelocity(velocity.getZ()), encodeAngle(location.getPitch()), encodeAngle(location.getYaw()), ENTITY_TYPE_IDS.get(type), data); } public static Packet spawnPlayerPacket(int entityId, UUID uuid, Location location, Player player) { return new PacketPlayOutNamedEntitySpawn(entityId, uuid, location.getX(), location.getY(), location.getZ(), encodeAngle(location.getYaw()), encodeAngle(location.getPitch()), copyEntityMetadata(player)); } public static Packet spawnPlayerPacket(int entityId, UUID uuid, Location location, List<DataWatcher.Item<?>> metadata) { return new PacketPlayOutNamedEntitySpawn(entityId, uuid, location.getX(), location.getY(), location.getZ(), encodeAngle(location.getYaw()), encodeAngle(location.getPitch()), metadata); } public static Packet setPassengerPacket(int vehicleId, int riderId) { return new PacketPlayOutMount(vehicleId, riderId); } public static Packet moveEntityRelativePacket(int entityId, Vector delta, boolean onGround) { return new PacketPlayOutEntity.PacketPlayOutRelEntityMove(entityId, encodePosition(delta.getX()), encodePosition(delta.getY()), encodePosition(delta.getZ()), onGround); } public static Packet teleportEntityPacket(int entityId, Location location) { return new PacketPlayOutEntityTeleport(entityId, location.getX(), location.getY(), location.getZ(), encodeAngle(location.getYaw()), encodeAngle(location.getPitch()), true); } private static Packet entityMetadataPacket(int entityId, net.minecraft.server.Entity nmsEntity, boolean complete) { return new PacketPlayOutEntityMetadata(entityId, nmsEntity.getDataWatcher(), complete); } public static Packet entityMetadataPacket(int entityId, Entity entity, boolean complete) { return entityMetadataPacket(entityId, ((CraftEntity) entity).getHandle(), complete); } public static Packet entityMetadataPacket(net.minecraft.server.Entity nmsEntity, boolean complete) { return entityMetadataPacket(nmsEntity.getId(), nmsEntity, complete); } public static Packet entityHelmetPacket(int entityId, org.bukkit.inventory.ItemStack helmet) { return new PacketPlayOutEntityEquipment(entityId, EnumItemSlot.HEAD, CraftItemStack.asNMSCopy(helmet)); } /** * Immediately send the given entity's metadata to all viewers in range */ public static void sendEntityMetadataToViewers(Entity entity, boolean complete) { sendPacketToViewers(entity, entityMetadataPacket(entity.getEntityId(), entity, complete)); } public interface FakeEntity { int entityId(); void spawn(Player viewer, Location location, Vector velocity); default void spawn(Player viewer, Location location) { spawn(viewer, location, Vectors.ZERO); } default void destroy(Player viewer) { sendPacket(viewer, destroyEntitiesPacket(entityId())); } default void move(Player viewer, Vector delta, boolean onGround) { sendPacket(viewer, moveEntityRelativePacket(entityId(), delta, onGround)); } default void teleport(Player viewer, Location location) { sendPacket(viewer, teleportEntityPacket(entityId(), location)); } default void ride(Player viewer, Entity rider) { sendPacket(viewer, setPassengerPacket(entityId(), rider.getEntityId())); } } private static abstract class FakeEntityImpl<T extends net.minecraft.server.Entity> implements FakeEntity { protected final T entity; protected FakeEntityImpl(T entity) { this.entity = entity; } @Override public int entityId() { return entity.getId(); } } public static class FakeArmorStand extends FakeEntityImpl<EntityArmorStand> { public FakeArmorStand(World world) { this(world, null); } public FakeArmorStand(World world, @Nullable String name) { this(world, name, true, true, true); } public FakeArmorStand(World world, @Nullable String name, boolean invisible, boolean marker, boolean small) { super(new EntityArmorStand(((CraftWorld) world).getHandle())); entity.setInvisible(invisible); entity.setMarker(marker); entity.setSmall(small); entity.setBasePlate(false); entity.setArms(false); if(name != null) { entity.setCustomName(name); entity.setCustomNameVisible(true); } } @Override public void spawn(Player viewer, Location location, Vector velocity) { sendPacket(viewer, spawnEntityPacket(ArmorStand.class, 0, entityId(), entity.getUniqueID(), location, velocity)); sendPacket(viewer, entityMetadataPacket(entity, true)); } } private static class FakeLivingEntity<T extends EntityLiving> extends FakeEntityImpl<T> { protected FakeLivingEntity(T entity) { super(entity); } @Override public void spawn(Player viewer, Location location, Vector velocity) { entity.setPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); entity.motX = velocity.getX(); entity.motY = velocity.getY(); entity.motZ = velocity.getZ(); sendPacket(viewer, spawnPacket()); } protected Packet<?> spawnPacket() { return new PacketPlayOutSpawnEntityLiving(entity); } } public static class FakeZombie extends FakeLivingEntity<EntityZombie> { public FakeZombie(World world, boolean invisible) { super(new EntityZombie(((CraftWorld) world).getHandle())); entity.setInvisible(invisible); entity.setAI(true); } } public static class FakeChicken extends FakeLivingEntity<EntityChicken> { public FakeChicken(World world, String name) { super(new EntityChicken(((CraftWorld) world).getHandle())); entity.setCustomName(name); entity.setCustomNameVisible(true); } @Override public void spawn(Player viewer, Location location, Vector velocity) { super.spawn(viewer, location, velocity); sendPacket(viewer, entityMetadataPacket(entity, true)); } } public static class FakePlayer implements FakeEntity { private static @Nullable EntityPlayer prototype; private static EntityPlayer prototype() { if(prototype == null) { final MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer(); final net.minecraft.server.WorldServer nmsWorld = nmsServer.worldServer[0]; prototype = new EntityPlayer(nmsServer, nmsWorld, new GameProfile(UUID.randomUUID(), ""), new PlayerInteractManager(nmsWorld)); prototype.setInvisible(true); } return prototype; } private final int entityId; private final UUID uuid; private @Nullable Packet destroyPacket; private @Nullable Location location; private @Nullable Packet teleportPacket; private @Nullable org.bukkit.inventory.ItemStack helmet; private @Nullable Packet helmetPacket; public FakePlayer() { this(Bukkit.allocateEntityId(), UUID.randomUUID()); } public FakePlayer(int entityId, UUID uuid) { this.entityId = entityId; this.uuid = uuid; } @Override public int entityId() { return entityId; } @Override public void spawn(Player viewer, Location location, Vector velocity) { spawn(viewer, uuid, location); if(!velocity.isZero()) { sendPacket(viewer, new PacketPlayOutEntityVelocity(entityId(), velocity.getX(), velocity.getY(), velocity.getZ())); } } public void spawn(Player viewer, UUID uuid, Location location) { sendPacket(viewer, spawnPlayerPacket(entityId(), uuid, location, prototype().getDataWatcher().c())); } private Packet destroyPacket() { if(destroyPacket == null) { destroyPacket = destroyEntitiesPacket(entityId()); } return destroyPacket; } @Override public void destroy(Player viewer) { sendPacket(viewer, destroyPacket()); } @Override public void teleport(Player viewer, Location location) { if(!Objects.equals(this.location, location)) { this.location = location.clone(); teleportPacket = teleportEntityPacket(entityId(), location); } sendPacket(viewer, teleportPacket); } public void wearHelmet(Player viewer, org.bukkit.inventory.ItemStack helmet) { if(!Objects.equals(this.helmet, helmet)) { this.helmet = helmet.clone(); helmetPacket = entityHelmetPacket(entityId(), helmet); } sendPacket(viewer, helmetPacket); } } private enum MetadataFlag { BURNING(0x01), SNEAKING(0x02), SPRINTING(0x08), EATING(0x10), INVISIBLE(0x20), GLOWING(0x40), GLIDING(0x80); final int mask; MetadataFlag(int mask) { this.mask = mask; } } private enum ArmorStandFlag { SMALL(0x01), GRAVITY(0x02), ARMS(0x04), BASEPLATE(0x08), MARKER(0x10); final int mask; ArmorStandFlag(int mask) { this.mask = mask; } } private static double randomEntityVelocity() { return random.nextDouble() - 0.5d; } public static void showFakeItems(Plugin plugin, Player viewer, Location location, org.bukkit.inventory.ItemStack item, int count, Duration duration) { if(count <= 0) return; final EntityPlayer nmsPlayer = ((CraftPlayer) viewer).getHandle(); final int[] entityIds = new int[count]; for(int i = 0; i < count; i++) { final EntityItem entity = new EntityItem(nmsPlayer.getWorld(), location.getX(), location.getY(), location.getZ(), CraftItemStack.asNMSCopy(item)); entity.motX = randomEntityVelocity(); entity.motY = randomEntityVelocity(); entity.motZ = randomEntityVelocity(); sendPacket(viewer, new PacketPlayOutSpawnEntity(entity, ENTITY_TYPE_IDS.get(org.bukkit.entity.Item.class))); sendPacket(viewer, new PacketPlayOutEntityMetadata(entity.getId(), entity.getDataWatcher(), true)); entityIds[i] = entity.getId(); } scheduleEntityDestroy(plugin, viewer.getUniqueId(), duration, entityIds); } private static void scheduleEntityDestroy(Plugin plugin, UUID viewerUuid, Duration delay, int[] entityIds) { plugin.getServer().getScheduler().runTaskLater(plugin, () -> { final Player viewer = plugin.getServer().getPlayer(viewerUuid); if(viewer != null) { sendPacket(viewer, new PacketPlayOutEntityDestroy(entityIds)); } }, TimeUtils.toTicks(delay)); } public static void setFireworksExpectedLifespan(Firework firework, int ticks) { ((CraftFirework) firework).getHandle().expectedLifespan = ticks; } public static void setFireworksTicksFlown(Firework firework, int ticks) { EntityFireworks entityFirework = ((CraftFirework) firework).getHandle(); entityFirework.ticksFlown = ticks; } public static void skipFireworksLaunch(Firework firework) { setFireworksExpectedLifespan(firework, 2); setFireworksTicksFlown(firework, 2); sendEntityMetadataToViewers(firework, false); } public static void playEffect(World bukkitWorld, Vector pos, int effectId, int data) { WorldServer world = ((CraftWorld) bukkitWorld).getHandle(); world.triggerEffect(effectId, new BlockPosition(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()), data); } @SuppressWarnings("deprecation") public static void playBlockBreakEffect(World bukkitWorld, Vector pos, Material material) { playEffect(bukkitWorld, pos, 2001, material.getId()); } private static void playCustomSound(World bukkitWorld, Location location, SoundEffect sound, SoundCategory category, Float volume, Float pitch) { WorldServer world = ((CraftWorld) bukkitWorld).getHandle(); world.playSoundEffect(null, location.getX(), location.getY(), location.getZ(), sound, category, volume, pitch); } public static void playBlockPlaceSound(World bukkitWorld, Vector pos, Material material, float volume) { if (!material.isBlock()) { return; } playCustomSound(bukkitWorld, pos.toLocation(bukkitWorld), CraftMagicNumbers.getBlock(material).getStepSound().placeSound(), SoundCategory.BLOCKS, volume, 1f); } /** * Test if the given tool is capable of "efficiently" mining the given block. * * Derived from CraftBlock.itemCausesDrops() */ public static boolean canMineBlock(MaterialData blockMaterial, org.bukkit.inventory.ItemStack tool) { if(!blockMaterial.getItemType().isBlock()) { throw new IllegalArgumentException("Material '" + blockMaterial + "' is not a block"); } net.minecraft.server.Block nmsBlock = CraftMagicNumbers.getBlock(blockMaterial.getItemType()); net.minecraft.server.Item nmsTool = tool == null ? null : CraftMagicNumbers.getItem(tool.getType()); return nmsBlock != null && (nmsBlock.getBlockData().getMaterial().isAlwaysDestroyable() || (nmsTool != null && nmsTool.canDestroySpecialBlock(nmsBlock.getBlockData()))); } public static long getMonotonicTime(World world) { return ((CraftWorld) world).getHandle().getTime(); } public static void createExplosion(Entity entity, Location loc, float power, boolean fire, boolean destroy) { ((CraftWorld) loc.getWorld()).getHandle().createExplosion(((CraftEntity) entity).getHandle(), loc.getX(), loc.getY(), loc.getZ(), power, fire, destroy); } /** * Test if a {@link Skull} has a cached skin. If this returns false, the skull will * likely try to fetch its skin the next time it is loaded. */ public static boolean isSkullCached(Skull skull) { TileEntitySkull nmsSkull = (TileEntitySkull) ((CraftWorld) skull.getWorld()).getTileEntityAt(skull.getX(), skull.getY(), skull.getZ()); return nmsSkull.getGameProfile() == null || nmsSkull.getGameProfile().getProperties().containsKey("textures"); } public static void enableArmorSlots(ArmorStand armorStand, boolean enabled) { CraftArmorStand craftArmorStand = (CraftArmorStand) armorStand; NBTTagCompound nbt = new NBTTagCompound(); craftArmorStand.getHandle().b(nbt); nbt.setInt("DisabledSlots", enabled ? 0 : 0x1f1f00); craftArmorStand.getHandle().a(nbt); } public static Object particlesPacket(String name, boolean longRange, Vector pos, Vector offset, float data, int count, int... extra) { return new PacketPlayOutWorldParticles(EnumParticle.valueOf(EnumParticle.class, name), longRange, (float) pos.getX(), (float) pos.getY(), (float) pos.getZ(), (float) offset.getX(), (float) offset.getY(), (float) offset.getZ(), data, count, extra); } public static Object blockCrackParticlesPacket(MaterialData material, boolean longRange, Vector pos, Vector offset, float data, int count) { return particlesPacket("BLOCK_CRACK", longRange, pos, offset, data, count, material.getItemTypeId() + (material.getData() << 12)); } public static void showBorderWarning(Player player, boolean show) { WorldBorder border = new WorldBorder(); border.setWarningDistance(show ? Integer.MAX_VALUE : 0); sendPacket(player, new PacketPlayOutWorldBorder(border, PacketPlayOutWorldBorder.EnumWorldBorderAction.SET_WARNING_BLOCKS)); } public static void playDeathAnimation(Player player) { EntityPlayer handle = ((CraftPlayer) player).getHandle(); PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(handle.getId(), handle.getDataWatcher(), false); // Add/replace health to zero boolean replaced = false; DataWatcher.Item<Float> zeroHealth = new DataWatcher.Item<>(EntityPlayer.class, EntityLiving.HEALTH, 0f); if(packet.b != null) { for(int i = 0; i < packet.b.size(); i++) { DataWatcher.Item<?> item = packet.b.get(i); if(EntityLiving.HEALTH.equals(item.a())) { packet.b.set(i, zeroHealth); replaced = true; } } } if(!replaced) { if(packet.b == null) { packet.b = Collections.singletonList(zeroHealth); } else { packet.b.add(zeroHealth); } } sendPacketToViewers(player, packet); } public static void sendDeathEffects(Player player, boolean blackout) { sendPacket(player, new PacketPlayOutEntityEffect(player.getEntityId(), new MobEffect(MobEffectList.getByName("nausea"), 100, 0, true, false))); sendPacket(player, new PacketPlayOutEntityEffect(player.getEntityId(), new MobEffect(MobEffectList.getByName("blindness"), blackout ? Integer.MAX_VALUE : 21, 0, true, false))); } public static void clearDeathEffects(Player player) { sendPacket(player, new PacketPlayOutRemoveEntityEffect(player.getEntityId(), MobEffectList.getByName("nausea"))); sendPacket(player, new PacketPlayOutRemoveEntityEffect(player.getEntityId(), MobEffectList.getByName("blindness"))); } static ItemStack asNMS(org.bukkit.inventory.ItemStack bukkit) { if(bukkit instanceof CraftItemStack) { return ((CraftItemStack) bukkit).getHandle(); } else { return CraftItemStack.asNMSCopy(bukkit); } } public static String getKey(Material material) { MinecraftKey key = Item.REGISTRY.b(CraftMagicNumbers.getItem(material)); return key == null ? null : key.toString(); } public static @Nullable Material materialByKey(String key) { final Item item = Item.REGISTRY.get(new MinecraftKey(key)); return item == null ? null : CraftMagicNumbers.getMaterial(item); } public static String getTranslationKey(org.bukkit.inventory.ItemStack stack) { ItemStack nms = asNMS(stack); return nms == null || nms.getItem().equals(Items.a) // Items.a == AIR ? null : nms.getItem().j(nms) + ".name"; } private static String getTranslationKey(Block nmsBlock) { return nmsBlock.a() + ".name"; } // Some materials cannot be made into NMS ItemStacks (e.g. Lava), // so try to make them directly into blocks instead. private static @Nullable String getBlockTranslationKey(Material material) { Block nmsBlock = CraftMagicNumbers.getBlock(material); return nmsBlock == null ? null : getTranslationKey(nmsBlock); } public static @Nullable String getTranslationKey(Material material) { String key = getTranslationKey(new org.bukkit.inventory.ItemStack(material)); return key != null ? key : getBlockTranslationKey(material); } public static @Nullable String getTranslationKey(MaterialData material) { String key = getTranslationKey(material.toItemStack(1)); return key != null ? key : getBlockTranslationKey(material.getItemType()); } public static String getTranslationKey(Entity entity) { if(entity instanceof TNTPrimed) { return "tile.tnt.name"; } else if(entity instanceof Egg) { return "item.egg.name"; } else { final String id = EntityTypes.b(((CraftEntity) entity).getHandle()); return "entity." + (id != null ? id : "generic") + ".name"; } } public static String getTranslationKey(PotionEffectType effect) { if(effect instanceof CraftPotionEffectType) { return ((CraftPotionEffectType) effect).getHandle().a(); } else if(effect instanceof PotionEffectTypeWrapper) { return getTranslationKey(((PotionEffectTypeWrapper) effect).getType()); } else { return "potion.empty"; } } public static org.bukkit.enchantments.Enchantment getEnchantment(String key) { Enchantment enchantment = Enchantment.b(key); return enchantment == null ? null : org.bukkit.enchantments.Enchantment.getById(Enchantment.getId(enchantment)); } public static Key getKey(org.bukkit.enchantments.Enchantment enchantment) { return CraftKey.get(Enchantment.enchantments.b(Enchantment.c(enchantment.getId()))); } public static Set<MaterialData> getBlockStates(Material material) { ImmutableSet.Builder<MaterialData> materials = ImmutableSet.builder(); Block nmsBlock = CraftMagicNumbers.getBlock(material); List<IBlockData> states = nmsBlock.s().a(); if(states != null) { for(IBlockData state : states) { int data = nmsBlock.toLegacyData(state); materials.add(material.getNewData((byte) data)); } } return materials.build(); } public static String getKey(Sound bukkitSound) { return CraftSound.getSound(bukkitSound); } public static void useEntity(Player user, Entity target, boolean attack, EquipmentSlot hand) { ((CraftPlayer) user).getHandle().playerConnection.a(new PacketPlayInUseEntity( target.getEntityId(), attack ? PacketPlayInUseEntity.EnumEntityUseAction.ATTACK : PacketPlayInUseEntity.EnumEntityUseAction.INTERACT, null, hand == EquipmentSlot.OFF_HAND ? EnumHand.OFF_HAND : EnumHand.MAIN_HAND )); } public static long lastTickDurationNanos() { // MinecraftServer.h is a circular buffer with the last 100 tick times // in nanoseconds, and MinecraftServer.aq() returns the iterator. The // iterator is incremented at the start of the tick, and the buffer is // written to at the end, so backing it up by one should give us the // most recent sample. int i = MinecraftServer.getServer().aq() % 100; if(--i < 0) i = 99; return MinecraftServer.getServer().h[i]; } public static int playerLatencyMillis(Player player) { return ((CraftPlayer) player).getHandle().ping; } }