package net.mc_pandacraft.java.plugin.pandacraftutils.protocol; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; /** * ParticleEffect Library *

* This library was created by @DarkBlade12 based on content related to particles of @microgeek (names and packet parameters), it allows you to display all Minecraft particle effects on a Bukkit server *

* You are welcome to use it, modify it and redistribute it under the following conditions: *

*

* It would be nice if you provide credit to me if you use this class in a published project * * @author DarkBlade12 * @version 1.5 */ public enum ParticleEffect { /** * A particle effect which is displayed by exploding tnt and creepers: *

*/ HUGE_EXPLOSION("hugeexplosion"), /** * A particle effect which is displayed by exploding ghast fireballs and wither skulls: * */ LARGE_EXPLODE("largeexplode"), /** * A particle effect which is displayed by launching fireworks: * */ FIREWORKS_SPARK("fireworksSpark"), /** * A particle effect which is displayed by swimming entities and arrows in water: * */ BUBBLE("bubble", true), /** * A particle effect which is displayed by water: * */ SUSPEND("suspend", true), /** * A particle effect which is displayed by air when close to bedrock and the in the void: * */ DEPTH_SUSPEND("depthSuspend"), /** * A particle effect which is displayed by mycelium: * */ TOWN_AURA("townaura"), /** * A particle effect which is displayed when landing a critical hit and by arrows: * */ CRIT("crit"), /** * A particle effect which is displayed when landing a hit with an enchanted weapon: * */ MAGIC_CRIT("magicCrit"), /** * A particle effect which is displayed by primed tnt, torches, droppers, dispensers, end portals, brewing stands and monster spawners: * */ SMOKE("smoke"), /** * A particle effect which is displayed by entities with active potion effects: * */ MOB_SPELL("mobSpell"), /** * A particle effect which is displayed by entities with active potion effects applied through a beacon: * */ MOB_SPELL_AMBIENT("mobSpellAmbient"), /** * A particle effect which is displayed when splash potions or bottles o' enchanting hit something: * */ SPELL("spell"), /** * A particle effect which is displayed when instant splash potions hit something: * */ INSTANT_SPELL("instantSpell"), /** * A particle effect which is displayed by witches: * */ WITCH_MAGIC("witchMagic"), /** * A particle effect which is displayed by note blocks: * */ NOTE("note"), /** * A particle effect which is displayed by nether portals, endermen, ender pearls, eyes of ender, ender chests and dragon eggs: * */ PORTAL("portal"), /** * A particle effect which is displayed by enchantment tables which are nearby bookshelves: * */ ENCHANTMENT_TABLE("enchantmenttable"), /** * A particle effect which is displayed by exploding tnt and creepers: * */ EXPLODE("explode"), /** * A particle effect which is displayed by torches, active furnaces, magma cubes and monster spawners: * */ FLAME("flame"), /** * A particle effect which is displayed by lava: * */ LAVA("lava"), /** * A particle effect which is currently unused: * */ FOOTSTEP("footstep"), /** * A particle effect which is displayed by swimming entities, rain dropping on the ground and shaking wolves: * */ SPLASH("splash"), /** * A particle effect which is displayed on water when fishing: * */ WAKE("wake"), /** * A particle effect which is displayed by fire, minecarts with furnace and blazes: * */ LARGE_SMOKE("largesmoke"), /** * A particle effect which is displayed when a mob dies: * */ CLOUD("cloud"), /** * A particle effect which is displayed by redstone ore, powered redstone, redstone torches and redstone repeaters: * */ RED_DUST("reddust"), /** * A particle effect which is displayed when snowballs or eggs hit something: * */ SNOWBALL_POOF("snowballpoof"), /** * A particle effect which is displayed by blocks beneath a water source: * */ DRIP_WATER("dripWater"), /** * A particle effect which is displayed by blocks beneath a lava source: * */ DRIP_LAVA("dripLava"), /** * A particle effect which is currently unused: * */ SNOW_SHOVEL("snowshovel"), /** * A particle effect which is displayed by slimes: * */ SLIME("slime"), /** * A particle effect which is displayed when breeding and taming animals: * */ HEART("heart"), /** * A particle effect which is displayed when attacking a villager in a village: * */ ANGRY_VILLAGER("angryVillager"), /** * A particle effect which is displayed when using bone meal and trading with a villager in a village: * */ HAPPY_VILLAGER("happyVillager"); private static final Map NAME_MAP = new HashMap(); private final String name; private final boolean requiresWater; // Initialize map for quick name lookup static { for (ParticleEffect effect : values()) { NAME_MAP.put(effect.name, effect); } } /** * Construct a new particle effect * * @param name Name of this particle effect * @param requiresWater Indicates whether water is required for this particle effect to display properly */ private ParticleEffect(String name, boolean requiresWater) { this.name = name; this.requiresWater = requiresWater; } /** * Construct a new particle effect with {@link #requiresWater} set to false * * @param name Name of this particle effect * @see #ParticleEffect(String, boolean) */ private ParticleEffect(String name) { this(name, false); } /** * Returns the name of this particle effect * * @return The name */ public String getName() { return name; } /** * Determine if water is required for this particle effect to display properly * * @return Whether water is required or not */ public boolean getRequiresWater() { return requiresWater; } /** * Returns the particle effect with the given name * * @param name Name of the particle effect * @return The particle effect */ public static ParticleEffect fromName(String name) { for (Entry entry : NAME_MAP.entrySet()) { if (!entry.getKey().equalsIgnoreCase(name)) { continue; } return entry.getValue(); } return null; } /** * Determine if water is at a certain location * * @param location Location to check * @return Whether water is at this location or not */ private static boolean isWater(Location location) { Material material = location.getBlock().getType(); return material == Material.WATER || material == Material.STATIONARY_WATER; } /** * Determine if an id is a block id * * @param id Id to check * @return Whether id is a block or not */ @SuppressWarnings("deprecation") private static boolean isBlock(int id) { Material material = Material.getMaterial(id); return material != null && material.isBlock(); } /** * Displays a particle effect which is only visible for all players within a certain range in the world of @param center * * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param range Range of the visibility (Maximum range for particles is usually 16, but it can differ for some types) * @throws IllegalArgumentException If the particle effect requires water and none is at the center location * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, double) */ public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws IllegalArgumentException { if (requiresWater && !isWater(center)) { throw new IllegalArgumentException("There is no water at the center location"); } new ParticleEffectPacket(name, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, range); } /** * Displays a particle effect which is only visible for the specified players * * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param players Receivers of the effect * @throws IllegalArgumentException If the particle effect requires water and none is at the center location * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, List) */ public void display(float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws IllegalArgumentException { if (requiresWater && !isWater(center)) { throw new IllegalArgumentException("There is no water at the center location"); } new ParticleEffectPacket(name, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, players); } /** * Displays an icon crack (item break) particle effect which is only visible for all players within a certain range in the world of @param center * * @param id Id of the icon * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param range Range of the visibility (Maximum range for particles is usually 16, but it can differ for some types) * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, double) */ public static void displayIconCrack(int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) { new ParticleEffectPacket("iconcrack_" + id + "_" + data, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, range); } /** * Displays an icon crack (item break) particle effect which is only visible for the specified players * * @param id Id of the icon * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param players Receivers of the effect * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, List) */ public static void displayIconCrack(int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) { new ParticleEffectPacket("iconcrack_" + id + "_" + data, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, players); } /** * Displays a block crack (block break) particle effect which is only visible for all players within a certain range in the world of @param center * * @param id Id of the block * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param amount Amount of particles * @param center Center location of the effect * @param range Range of the visibility (Maximum range for particles is usually 16, but it can differ for some types) * @throws IllegalArgumentException If the specified id is not a block id * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, double) */ public static void displayBlockCrack(int id, byte data, float offsetX, float offsetY, float offsetZ, int amount, Location center, double range) throws IllegalArgumentException { if (!isBlock(id)) { throw new IllegalArgumentException("Invalid block id"); } new ParticleEffectPacket("blockcrack_" + id + "_" + data, offsetX, offsetY, offsetZ, 0, amount).sendTo(center, range); } /** * Displays a block crack (block break) particle effect which is only visible for the specified players * * @param id Id of the block * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param amount Amount of particles * @param center Center location of the effect * @param players Receivers of the effect * @throws IllegalArgumentException If the specified id is not a block id * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, List) */ public static void displayBlockCrack(int id, byte data, float offsetX, float offsetY, float offsetZ, int amount, Location center, List players) throws IllegalArgumentException { if (!isBlock(id)) { throw new IllegalArgumentException("Invalid block id"); } new ParticleEffectPacket("blockcrack_" + id + "_" + data, offsetX, offsetY, offsetZ, 0, amount).sendTo(center, players); } /** * Displays a block dust particle effect which is only visible for all players within a certain range in the world of @param center * * @param id Id of the block * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param range Range of the visibility (Maximum range for particles is usually 16, but it can differ for some types) * @throws IllegalArgumentException If the specified id is not a block id * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, double) */ public static void displayBlockDust(int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, double range) throws IllegalArgumentException { if (!isBlock(id)) { throw new IllegalArgumentException("Invalid block id"); } new ParticleEffectPacket("blockdust_" + id + "_" + data, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, range); } /** * Displays a block dust particle effect which is only visible for the specified players * * @param id Id of the block * @param data Data value * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @param center Center location of the effect * @param players Receivers of the effect * @throws IllegalArgumentException If the specified id is not a block id * @see ParticleEffectPacket * @see ParticleEffectPacket#sendTo(Location, List) */ public static void displayBlockDust(int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int amount, Location center, List players) throws IllegalArgumentException { if (!isBlock(id)) { throw new IllegalArgumentException("Invalid block id"); } new ParticleEffectPacket("blockdust_" + id + "_" + data, offsetX, offsetY, offsetZ, speed, amount).sendTo(center, players); } /** * Represents a particle effect packet with all attributes which is used for sending packets to the players *

* This class is part of the ParticleEffect Library and follows the same usage conditions * * @author DarkBlade12 * @since 1.5 */ public static final class ParticleEffectPacket { private static Constructor packetConstructor; private static Method getHandle; private static Field playerConnection; private static Method sendPacket; private static boolean initialized; private final String name; private final float offsetX; private final float offsetY; private final float offsetZ; private final float speed; private final int amount; private Object packet; /** * Construct a new particle effect packet * * @param name Name of the effect * @param offsetX Maximum distance particles can fly away from the center on the x-axis * @param offsetY Maximum distance particles can fly away from the center on the y-axis * @param offsetZ Maximum distance particles can fly away from the center on the z-axis * @param speed Display speed of the particles * @param amount Amount of particles * @throws IllegalArgumentException If the speed is lower than 0 or the amount is lower than 1 * @see #initialize() */ public ParticleEffectPacket(String name, float offsetX, float offsetY, float offsetZ, float speed, int amount) throws IllegalArgumentException { initialize(); if (speed < 0) { throw new IllegalArgumentException("The speed is lower than 0"); } if (amount < 1) { throw new IllegalArgumentException("The amount is lower than 1"); } this.name = name; this.offsetX = offsetX; this.offsetY = offsetY; this.offsetZ = offsetZ; this.speed = speed; this.amount = amount; } /** * Initializes {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} and sets {@link #initialized} to true if it succeeds *

* Note: These fields only have to be initialized once, so it will return if {@link #initialized} is already set to true * * @throws VersionIncompatibleException if accessed packets, fields or methods differ in your bukkit version */ public static void initialize() throws VersionIncompatibleException { if (initialized) { return; } try { int version = Integer.parseInt(Character.toString(ReflectionUtils.PackageType.getServerVersion().charAt(3))); Class packetClass = ReflectionUtils.PackageType.MINECRAFT_SERVER.getClass(version < 7 ? "Packet63WorldParticles" : ReflectionUtils.PacketType.PLAY_OUT_WORLD_PARTICLES.getName()); packetConstructor = ReflectionUtils.getConstructor(packetClass); getHandle = ReflectionUtils.getMethod("CraftPlayer", ReflectionUtils.PackageType.CRAFTBUKKIT_ENTITY, "getHandle"); playerConnection = ReflectionUtils.getField("EntityPlayer", ReflectionUtils.PackageType.MINECRAFT_SERVER, false, "playerConnection"); sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", ReflectionUtils.PackageType.MINECRAFT_SERVER.getClass("Packet")); } catch (Exception exception) { throw new VersionIncompatibleException("Your current bukkit version seems to be incompatible with this library", exception); } initialized = true; } /** * Determine if {@link #packetConstructor}, {@link #getHandle}, {@link #playerConnection} and {@link #sendPacket} are initialized * * @return Whether these fields are initialized or not * @see #initialize() */ public static boolean isInitialized() { return initialized; } /** * Sends the packet to a single player and caches it * * @param center Center location of the effect * @param player Receiver of the packet * @throws PacketInstantiationException if instantion fails due to an unknown error * @throws PacketSendingException if sending fails due to an unknown error */ public void sendTo(Location center, Player player) throws PacketInstantiationException, PacketSendingException { if (packet == null) { try { packet = packetConstructor.newInstance(); ReflectionUtils.setValue(packet, true, "a", name); ReflectionUtils.setValue(packet, true, "b", (float) center.getX()); ReflectionUtils.setValue(packet, true, "c", (float) center.getY()); ReflectionUtils.setValue(packet, true, "d", (float) center.getZ()); ReflectionUtils.setValue(packet, true, "e", offsetX); ReflectionUtils.setValue(packet, true, "f", offsetY); ReflectionUtils.setValue(packet, true, "g", offsetZ); ReflectionUtils.setValue(packet, true, "h", speed); ReflectionUtils.setValue(packet, true, "i", amount); } catch (Exception exception) { throw new PacketInstantiationException("Packet instantiation failed", exception); } } try { sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), packet); } catch (Exception exception) { throw new PacketSendingException("Failed to send the packet to player '" + player.getName() + "'", exception); } } /** * Sends the packet to all players in the list * * @param center Center location of the effect * @param players Receivers of the packet * @throws IllegalArgumentException If the player list is empty * @see #sendTo(Location center, Player player) */ public void sendTo(Location center, List players) throws IllegalArgumentException { if (players.isEmpty()) { throw new IllegalArgumentException("The player list is empty"); } for (Player player : players) { sendTo(center, player); } } /** * Sends the packet to all players in a certain range * * @param center Center location of the effect * @param range Range in which players will receive the packet (Maximum range for particles is usually 16, but it can differ for some types) * @throws IllegalArgumentException If the range is lower than 1 * @see #sendTo(Location center, Player player) */ public void sendTo(Location center, double range) throws IllegalArgumentException { if (range < 1) { throw new IllegalArgumentException("The range is lower than 1"); } String worldName = center.getWorld().getName(); double squared = range * range; for (Player player : Bukkit.getOnlinePlayers()) { if (!player.getWorld().getName().equals(worldName) || player.getLocation().distanceSquared(center) > squared) { continue; } sendTo(center, player); } } /** * Represents a runtime exception that is thrown if a bukkit version is not compatible with this library *

* This class is part of the ParticleEffect Library and follows the same usage conditions * * @author DarkBlade12 * @since 1.5 */ private static final class VersionIncompatibleException extends RuntimeException { private static final long serialVersionUID = 3203085387160737484L; /** * Construct a new version incompatible exception * * @param message Message that will be logged * @param cause Cause of the exception */ public VersionIncompatibleException(String message, Throwable cause) { super(message, cause); } } /** * Represents a runtime exception that is thrown if packet instantiation fails *

* This class is part of the ParticleEffect Library and follows the same usage conditions * * @author DarkBlade12 * @since 1.4 */ private static final class PacketInstantiationException extends RuntimeException { private static final long serialVersionUID = 3203085387160737484L; /** * Construct a new packet instantiation exception * * @param message Message that will be logged * @param cause Cause of the exception */ public PacketInstantiationException(String message, Throwable cause) { super(message, cause); } } /** * Represents a runtime exception that is thrown if packet sending fails *

* This class is part of the ParticleEffect Library and follows the same usage conditions * * @author DarkBlade12 * @since 1.4 */ private static final class PacketSendingException extends RuntimeException { private static final long serialVersionUID = 3203085387160737484L; /** * Construct a new packet sending exception * * @param message Message that will be logged * @param cause Cause of the exception */ public PacketSendingException(String message, Throwable cause) { super(message, cause); } } } }