├── .github ├── FUNDING.yml └── stale.yml ├── .travis.yml ├── pom.xml ├── renovate.json ├── settings.xml └── src └── main ├── java ├── de │ └── inventivegames │ │ └── particle │ │ └── ParticleEffect.java └── org │ └── inventivetalent │ └── particle │ ├── ParticleEffect.java │ ├── ParticleException.java │ └── ParticlePlugin.java └── resources └── plugin.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: InventivetalentDev 2 | patreon: inventivetalent 3 | custom: ["https://www.paypal.me/inventivetalent", "https://donation.inventivetalent.org"] 4 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | script: "mvn deploy --settings settings.xml" 6 | env: 7 | global: 8 | - secure: "IZ3oqCMkToBV7Pv5vfj5R86JpL422+grm56mI+BPO2e+7NPil9qfh0Gw5Z7rpQuH3pURgk37xcLF/E5PsSKJiBVXaoWvyQUczIYE3e3NhyQCJ4mtXSZ0dXpvdqQunAPhqcw5MZpKiI03rqOJbJTLvBX0J5aMOQM3pW9R5J5S/B/fgQXclvbR1u09UdUU6j0+Y8Jky1pyl9R9E7mJZMHvbXnAvPgbHyBncOiY58vZSA9s0Q4C05nH35S1THj7Nb2uZsm0Y1gjCyPYGz3YGna/4uwVIr3Lc1x/BzPXryryhf8x+2SNicZHNMyEhvHHkstlexeA28iaPCHqAxMvLV/OraZhE2yhbg1WvfkdQafnLRmR08+mXuQoP+APnzM1uYosf27FAKUC59UWOKaITvQQj47dccN3MNVTEKsZ+aqA0PX88lAGPXePlsymEEZxj6FAeCfX3jAWfZ4zUhHoldzF/Id+d/loN+0d8AuOVopnK4kPNhKThsgc7vmWVzKKpvCJ5QC6+isaROHcC8dw6fTxk2sLBypPyu8yRgIRZmWFdCV+TPL8IgZTdscnyUOwbODcpAERG3WNPATId85QnNwVY1CRizNzdznbo2VTf2EivKMjTCxZFzOe+EGEEGcAAviJITNe+mEDHiCN4RK3BPk3x7+MynIVzZJymOd39/BKHlY=" 9 | - secure: "xi/uz84SwYXyrnQAO+7o7PkeIS7vKilwLYOC13fewuoVAT/tSXFal945O1VA7kLshnpoZ+oKijfne6J7GJEBceRqGPEQ88gkqkF+6A7Isf/k/07dNIBlOIU6FNHeLcdslM+ahrbAqXolhuHq3p3cXzEhD8JLS2E6EVaTrlUOFpejFlkXrQnetSiymmCRhV4yZ62PKxZNUwRM49OKHKrHcF/TOBTQLsWuGwPv9AMrV++SqhwbTDzlKHibNqtyGOsygnreL6qPv10BjZoOrQ7df4qnKJTEMlY+VqoZvXBfQczNX0CzpIm0ZzTcZ2JxabUSxlRhutPAkTFKt0VfvDFV0zMeIToUNev8IUBCRGuqAz1vgtkdPk+0QRqFYRCYqR3T5j/eqU0R8K17l8kfE//qtpeNDBgZ91o1LnXxDZH9LiFJNn78JpHQvXLg2twGd7hy48mjbD0ogd81rLxgtRG3z1p6NCIA7qqiM4fk/om5WZxeSjpmNUzX4qyNq2OYoNtnvQ9mjj3TH8nQ4n1pmP18bCULTI1ZiTfSgkaKwZVDjN6v4nts42qBRXfVKCtHmRBZ+jT7mrb4FJgcz6D97kz2VFmhTUyIzBcBDaWF7YDw/REYh3mWZ3jxYSy6AUQM6aoaFNJnMFXsBra9azp0WwfiNvYCa9c9zpWr86Y3nxO6KzQ=" 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 28 | 29 | 31 | 4.0.0 32 | org.inventivetalent 33 | particleapi 34 | 2.1.2 35 | ParticleAPI 36 | 37 | 38 | ParticleAPI_v${project.version} 39 | src/main/java 40 | 41 | 42 | src/main/java 43 | 44 | **/*.java 45 | 46 | 47 | 48 | src/main/resources 49 | true 50 | 51 | plugin.yml 52 | config.yml 53 | 54 | 55 | 56 | 57 | 58 | maven-compiler-plugin 59 | 3.3 60 | 61 | 1.7 62 | 1.7 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-shade-plugin 68 | 2.4 69 | 70 | 71 | package 72 | 73 | shade 74 | 75 | 76 | 77 | 78 | org.mcstats.* 79 | org.inventivetalent:particleapi** 80 | org.inventivetalent:apimanager** 81 | org.inventivetalent:reflectionhelper** 82 | 83 | 84 | 85 | 86 | org.mcstats 87 | org.inventivetalent.particle.mcstats 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | sonatype-nexus-releases 99 | http://repo.inventivetalent.org/content/repositories/releases/ 100 | 101 | 102 | sonatype-nexus-snapshots 103 | http://repo.inventivetalent.org/content/repositories/snapshots/ 104 | 105 | 106 | 107 | 108 | org.spigotmc 109 | spigot-api 110 | 1.11-R0.1-SNAPSHOT 111 | provided 112 | 113 | 114 | org.inventivetalent 115 | reflectionhelper 116 | LATEST 117 | 118 | 119 | 120 | 121 | 122 | inventive-repo 123 | https://repo.inventivetalent.org/content/groups/public/ 124 | 125 | 126 | md_5-repo 127 | http://repo.md-5.net/content/repositories/public/ 128 | 129 | 130 | sonatype 131 | https://oss.sonatype.org/content/groups/public/ 132 | 133 | 134 | spigot-repo 135 | https://hub.spigotmc.org/nexus/content/groups/public/ 136 | 137 | 138 | sk89q-repo 139 | http://maven.sk89q.com/artifactory/repo/ 140 | 141 | 142 | techcable-repo 143 | http://repo.techcable.net/content/groups/public/ 144 | 145 | 146 | mcstats-repo 147 | http://repo.mcstats.org/content/groups/public/ 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "maven": { 6 | "enabled": true 7 | }, 8 | "ignoreUnstable": false, 9 | "hostRules": [{ 10 | "hostType": "maven", 11 | "endpoint": "https://repo.inventivetalent.org/content/groups/public/" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-releases 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | sonatype-nexus-snapshots 10 | ${env.CI_DEPLOY_USERNAME} 11 | ${env.CI_DEPLOY_PASSWORD} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/de/inventivegames/particle/ParticleEffect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 inventivetalent. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package de.inventivegames.particle; 30 | 31 | import org.bukkit.Bukkit; 32 | import org.bukkit.Location; 33 | import org.bukkit.entity.Player; 34 | 35 | import java.lang.reflect.Field; 36 | import java.lang.reflect.Method; 37 | import java.util.Collection; 38 | 39 | /** 40 | * @deprecated Please use org.inventivetalent.particle.ParticleEffect to get support for 1.9+ Particles 41 | */ 42 | @Deprecated 43 | public enum ParticleEffect { 44 | 45 | // == Support for 1.7 Implementation == 46 | HUGE_EXPLOSION("hugeexplosion", "EXPLOSION_HUGE"), 47 | LARGE_EXPLODE("largeexplode", "EXPLOSION_LARGE"), 48 | BUBBLE("bubble", "WATER_BUBBLE"), 49 | SUSPEND("suspended", "SUSPENDED"), 50 | DEPTH_SUSPEND("depthsuspend", "SUSPENDED_DEPTH"), 51 | MAGIC_CRIT("magicCrit", "CRIT_MAGIC"), 52 | MOB_SPELL("mobSpell", "SPELL_MOB", true), 53 | MOB_SPELL_AMBIENT("mobSpellAmbient", "SPELL_MOB_AMBIENT"), 54 | INSTANT_SPELL("instantSpell", "SPELL_INSTANT"), 55 | WITCH_MAGIC("witchMagic", "SPELL_WITCH"), 56 | EXPLODE("explode", "EXPLOSION_NORMAL"), 57 | SPLASH("splash", "WATER_SPLASH"), 58 | LARGE_SMOKE("largesmoke", "SMOKE_LARGE"), 59 | /** 60 | * - Colored 61 | */ 62 | RED_DUST("reddust", "REDSTONE", true), 63 | SNOWBALL_POOF("snowballpoof", "SNOWBALL"), 64 | ANGRY_VILLAGER("angryVillager", "VILLAGER_ANGRY"), 65 | HAPPY_VILLAGER("happyVillager", "VILLAGER_HAPPY"), 66 | // == 1.8 Particles and Supported 1.7 Particles == 67 | /** 68 | * @see #EXPLODE 69 | */ 70 | EXPLOSION_NORMAL(EXPLODE.getName()), 71 | /** 72 | * @see #LARGE_EXPLODE 73 | */ 74 | EXPLOSION_LARGE(LARGE_EXPLODE.getName()), 75 | /** 76 | * 1.8 only! 77 | * 78 | * @see #HUGE_EXPLOSION 79 | */ 80 | EXPLOSION_HUGE(HUGE_EXPLOSION.getName()), 81 | FIREWORKS_SPARK("fireworksSpark"), 82 | /** 83 | * @see #BUBBLE 84 | */ 85 | WATER_BUBBLE(BUBBLE.getName()), 86 | /** 87 | * @see #SPLASH 88 | */ 89 | WATER_SPLASH(SPLASH.getName()), 90 | /** 91 | * 1.8 only 92 | */ 93 | WATER_WAKE("wake"), 94 | /** 95 | * @see #SUSPEND 96 | */ 97 | SUSPENDED(SUSPEND.getName()), 98 | /** 99 | * @see #DEPTH_SUSPEND 100 | */ 101 | SUSPENDED_DEPTH(DEPTH_SUSPEND.getName()), 102 | CRIT("crit"), 103 | /** 104 | * @see #MAGIC_CRIT 105 | */ 106 | CRIT_MAGIC(MAGIC_CRIT.getName()), 107 | /** 108 | * 1.8 only! 109 | */ 110 | SMOKE_NORMAL("smoke"), 111 | /** 112 | * @see #LARGE_SMOKE 113 | */ 114 | SMOKE_LARGE(LARGE_SMOKE.getName()), 115 | SPELL("spell"), 116 | /** 117 | * @see #INSTANT_SPELL 118 | */ 119 | SPELL_INSTANT(INSTANT_SPELL.getName()), 120 | /** 121 | * - Colored 122 | * 123 | * @see #MOB_SPELL 124 | */ 125 | SPELL_MOB(MOB_SPELL.getName(), true), 126 | /** 127 | * @see #MOB_SPELL_AMBIENT 128 | */ 129 | SPELL_MOB_AMBIENT(MOB_SPELL_AMBIENT.getName()), 130 | /** 131 | * @see #WITCH_MAGIC 132 | */ 133 | SPELL_WITCH(WITCH_MAGIC.getName()), 134 | DRIP_WATER("dripWater"), 135 | DRIP_LAVA("dripLava"), 136 | /** 137 | * @see #ANGRY_VILLAGER 138 | */ 139 | VILLAGER_ANGRY(ANGRY_VILLAGER.getName()), 140 | /** 141 | * @see #HAPPY_VILLAGER 142 | */ 143 | VILLAGER_HAPPY(HAPPY_VILLAGER.getName()), 144 | TOWN_AURA("townaura"), 145 | /** 146 | * - Colored (Notes can be colored, but they're not fully implemented yet) 147 | */ 148 | NOTE("note", true), 149 | PORTAL("portal"), 150 | ENCHANTMENT_TABLE("enchantmenttable"), 151 | FLAME("flame"), 152 | LAVA("lava"), 153 | FOOTSTEP("footstep"), 154 | CLOUD("cloud"), 155 | REDSTONE("reddust", true), 156 | SNOWBALL("snowballpoof"), 157 | SNOW_SHOVEL("snowshovel"), 158 | SLIME("slime"), 159 | HEART("heart"), 160 | /** 161 | * 1.8 only! 162 | */ 163 | BARRIER("barrier"), 164 | /** 165 | * 1.8 only! 166 | */ 167 | ITEM_CRACK("iconcrack_"), 168 | /** 169 | * 1.8 only! 170 | */ 171 | BLOCK_CRACK("blockcrack_"), 172 | /** 173 | * 1.8 only! 174 | */ 175 | BLOCK_DUST("blockdust_"), 176 | /** 177 | * 1.8 only! 178 | */ 179 | WATER_DROP("droplet"), 180 | /** 181 | * 1.8 only! 182 | */ 183 | ITEM_TAKE("take"), 184 | /** 185 | * 1.8 only! 186 | */ 187 | MOB_APPEARANCE("mobappearance"); 188 | 189 | private String particleName; 190 | private String enumValue; 191 | private boolean hasColor; 192 | 193 | ParticleEffect(String particleName, String enumValue, boolean hasColor) { 194 | this.particleName = particleName; 195 | this.enumValue = enumValue; 196 | this.hasColor = hasColor; 197 | } 198 | 199 | ParticleEffect(String particleName, String enumValue) { 200 | this(particleName, enumValue, false); 201 | } 202 | 203 | ParticleEffect(String particleName) { 204 | this(particleName, null); 205 | } 206 | 207 | ParticleEffect(String particleName, boolean hasColor) { 208 | this(particleName, null, hasColor); 209 | } 210 | 211 | public String getName() { 212 | return this.particleName; 213 | } 214 | 215 | public boolean hasColor() { 216 | return hasColor; 217 | } 218 | 219 | private static Class nmsPacketPlayOutParticle = ReflectionUtilities.getNMSClass("PacketPlayOutWorldParticles"); 220 | private static Class nmsEnumParticle; 221 | private static int particleRange = 25; 222 | 223 | @Deprecated 224 | public static void setRange(int range) { 225 | if (range < 0) { throw new IllegalArgumentException("Range must be positive!"); } 226 | particleRange = range; 227 | } 228 | 229 | @Deprecated 230 | public static int getRange() { 231 | return particleRange; 232 | } 233 | 234 | @Deprecated 235 | private void sendToPlayer(Player player, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count, int... extra) throws Exception { 236 | sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, false, extra); 237 | } 238 | 239 | private void sendToPlayer(Player player, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force, int... extra) throws Exception { 240 | if (!force && !isPlayerInRange(player, location)) { return; } 241 | if (ReflectionUtilities.getVersion().contains("v1_8")) { 242 | try { 243 | if (nmsEnumParticle == null) { 244 | nmsEnumParticle = ReflectionUtilities.getNMSClass("EnumParticle"); 245 | } 246 | if (this == BLOCK_CRACK) { 247 | int id = 0; 248 | int data = 0; 249 | if (extra.length > 0) { 250 | id = extra[0]; 251 | } 252 | if (extra.length > 1) { 253 | data = extra[1]; 254 | } 255 | extra = new int[] { 256 | id, 257 | id | data << 12 }; 258 | } 259 | Object packet = nmsPacketPlayOutParticle.getConstructor(new Class[] { 260 | nmsEnumParticle, 261 | boolean.class, 262 | float.class, 263 | float.class, 264 | float.class, 265 | float.class, 266 | float.class, 267 | float.class, 268 | float.class, 269 | int.class, 270 | int[].class }).newInstance(getEnum(nmsEnumParticle.getName() + "." + (this.enumValue != null ? this.enumValue : this.name().toUpperCase())), true, (float) location.getX(), (float) location.getY(), (float) location.getZ(), offsetX, offsetY, offsetZ, speed, count, extra); 271 | Object handle = ReflectionUtilities.getHandle(player); 272 | Object connection = ReflectionUtilities.getField(handle.getClass(), "playerConnection").get(handle); 273 | ReflectionUtilities.getMethod(connection.getClass(), "sendPacket", new Class[0]).invoke(connection, new Object[] { packet }); 274 | } catch (Exception e) { 275 | // throw new IllegalArgumentException("Unable to send Particle " + name() + ". (Version 1.8): " + e.getMessage()); 276 | throw e; 277 | } 278 | } else { 279 | try { 280 | if (this.particleName == null) { 281 | this.particleName = this.name().toLowerCase(); 282 | } 283 | String name = this.particleName; 284 | if (this == BLOCK_CRACK || this == ITEM_CRACK || this == BLOCK_DUST) { 285 | int id = 0; 286 | int data = 0; 287 | if (extra.length > 0) { 288 | id = extra[0]; 289 | } 290 | if (extra.length > 1) { 291 | data = extra[1]; 292 | } 293 | name += id + "_" + data; 294 | } 295 | Object packet = nmsPacketPlayOutParticle.getConstructor(new Class[] { 296 | String.class, 297 | float.class, 298 | float.class, 299 | float.class, 300 | float.class, 301 | float.class, 302 | float.class, 303 | float.class, 304 | int.class }).newInstance(name, (float) location.getX(), (float) location.getY(), (float) location.getZ(), offsetX, offsetY, offsetZ, speed, count); 305 | Object handle = ReflectionUtilities.getHandle(player); 306 | Object connection = ReflectionUtilities.getField(handle.getClass(), "playerConnection").get(handle); 307 | ReflectionUtilities.getMethod(connection.getClass(), "sendPacket", new Class[0]).invoke(connection, new Object[] { packet }); 308 | } catch (Exception e) { 309 | // throw new IllegalArgumentException("Unable to send Particle " + name() + ". (Server Version: 1.7) " + e.getMessage()); 310 | throw e; 311 | } 312 | } 313 | } 314 | 315 | public void sendToPlayer(Player player, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 316 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, force, new int[0]); 317 | } 318 | 319 | @Deprecated 320 | public void sendToPlayer(Player player, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 321 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, false); 322 | } 323 | 324 | @Deprecated 325 | public void sendToPlayers(Collection players, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 326 | for (Player p : players) { 327 | this.sendToPlayer(p, location, offsetX, offsetY, offsetZ, speed, count); 328 | } 329 | } 330 | 331 | public void sendToPlayers(Collection players, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 332 | for (Player p : players) { 333 | this.sendToPlayer(p, location, offsetX, offsetY, offsetZ, speed, count, force); 334 | } 335 | } 336 | 337 | @Deprecated 338 | public void sendToPlayers(Player[] players, Location location, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 339 | for (Player p : players) { 340 | this.sendToPlayer(p, location, offsetX, offsetY, offsetZ, speed, count); 341 | } 342 | } 343 | 344 | // Color methods 345 | 346 | @Deprecated 347 | public void sendColor(Player p, Location location, org.bukkit.Color color) throws Exception { 348 | if (!this.hasColor) { return; } 349 | this.sendToPlayer(p, location, this.getColor(color.getRed()), this.getColor(color.getGreen()), this.getColor(color.getBlue()), 1, 0); 350 | } 351 | 352 | public void sendColor(Player p, Location location, org.bukkit.Color color, boolean force) throws Exception { 353 | if (!this.hasColor) { return; } 354 | this.sendToPlayer(p, location, this.getColor(color.getRed()), this.getColor(color.getGreen()), this.getColor(color.getBlue()), 1, 0, force); 355 | } 356 | 357 | @Deprecated 358 | public void sendColor(Player p, Location location, java.awt.Color color) throws Exception { 359 | if (!this.hasColor) { return; } 360 | this.sendToPlayer(p, location, this.getColor(color.getRed()), this.getColor(color.getGreen()), this.getColor(color.getBlue()), 1, 0); 361 | } 362 | 363 | public void sendColor(Player p, Location location, java.awt.Color color, boolean force) throws Exception { 364 | if (!this.hasColor) { return; } 365 | this.sendToPlayer(p, location, this.getColor(color.getRed()), this.getColor(color.getGreen()), this.getColor(color.getBlue()), 1, 0, force); 366 | } 367 | 368 | @Deprecated 369 | public void sendColor(Collection players, Location location, java.awt.Color color) throws Exception { 370 | if (!this.hasColor) { return; } 371 | for (Player p : players) { 372 | this.sendColor(p, location, color); 373 | } 374 | } 375 | 376 | public void sendColor(Collection players, Location location, java.awt.Color color, boolean force) throws Exception { 377 | if (!this.hasColor) { return; } 378 | for (Player p : players) { 379 | this.sendColor(p, location, color, force); 380 | } 381 | } 382 | 383 | public void sendColor(Collection players, Location location, org.bukkit.Color color) throws Exception { 384 | if (!this.hasColor) { return; } 385 | for (Player p : players) { 386 | this.sendColor(p, location, color); 387 | } 388 | } 389 | 390 | @Deprecated 391 | public void sendColor(Collection players, Location location, org.bukkit.Color color, boolean force) throws Exception { 392 | if (!this.hasColor) { return; } 393 | for (Player p : players) { 394 | this.sendColor(p, location, color, force); 395 | } 396 | } 397 | 398 | @Deprecated 399 | public void sendColor(Player[] players, Location location, org.bukkit.Color color) throws Exception { 400 | if (!this.hasColor) { return; } 401 | for (Player p : players) { 402 | this.sendColor(p, location, color); 403 | } 404 | } 405 | 406 | @Deprecated 407 | public void sendColor(Player[] players, Location location, java.awt.Color color) throws Exception { 408 | if (!this.hasColor) { return; } 409 | for (Player p : players) { 410 | this.sendColor(p, location, color); 411 | } 412 | } 413 | 414 | protected float getColor(float value) { 415 | if (value <= 0) { 416 | value = -1; 417 | } 418 | return value / 255; 419 | } 420 | 421 | // BLOCK_CRACK 422 | 423 | @Deprecated 424 | public void sendBlockCrack(Player player, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 425 | if (this != BLOCK_CRACK) { throw new IllegalArgumentException("This method is only available for BLOCK_CRACK!"); } 426 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, id, data); 427 | } 428 | 429 | public void sendBlockCrack(Player player, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 430 | if (this != BLOCK_CRACK) { throw new IllegalArgumentException("This method is only available for BLOCK_CRACK!"); } 431 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, force, id, data); 432 | } 433 | 434 | @Deprecated 435 | public void sendBlockCrack(Collection players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 436 | if (this != BLOCK_CRACK) { throw new IllegalArgumentException("This method is only available for BLOCK_CRACK!"); } 437 | for (Player p : players) { 438 | this.sendBlockCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count); 439 | } 440 | } 441 | 442 | public void sendBlockCrack(Collection players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 443 | if (this != BLOCK_CRACK) { throw new IllegalArgumentException("This method is only available for BLOCK_CRACK!"); } 444 | for (Player p : players) { 445 | this.sendBlockCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count, force); 446 | } 447 | } 448 | 449 | @Deprecated 450 | public void sendBlockCrack(Player[] players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 451 | if (this != BLOCK_CRACK) { throw new IllegalArgumentException("This method is only available for BLOCK_CRACK!"); } 452 | for (Player p : players) { 453 | this.sendBlockCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count); 454 | } 455 | } 456 | 457 | // ITEM_CRACK 458 | 459 | @Deprecated 460 | public void sendItemCrack(Player player, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 461 | if (this != ITEM_CRACK) { throw new IllegalArgumentException("This method is only available for ITEM_CRACK!"); } 462 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, id, data); 463 | } 464 | 465 | public void sendItemCrack(Player player, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 466 | if (this != ITEM_CRACK) { throw new IllegalArgumentException("This method is only available for ITEM_CRACK!"); } 467 | this.sendToPlayer(player, location, offsetX, offsetY, offsetZ, speed, count, force, id, data); 468 | } 469 | 470 | @Deprecated 471 | public void sendItemCrack(Collection players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 472 | if (this != ITEM_CRACK) { throw new IllegalArgumentException("This method is only available for ITEM_CRACK!"); } 473 | for (Player p : players) { 474 | this.sendItemCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count); 475 | } 476 | } 477 | 478 | public void sendItemCrack(Collection players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 479 | if (this != ITEM_CRACK) { throw new IllegalArgumentException("This method is only available for ITEM_CRACK!"); } 480 | for (Player p : players) { 481 | this.sendItemCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count, force); 482 | } 483 | } 484 | 485 | @Deprecated 486 | public void sendItemCrack(Player[] players, Location location, int id, byte data, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 487 | if (this != ITEM_CRACK) { throw new IllegalArgumentException("This method is only available for ITEM_CRACK!"); } 488 | for (Player p : players) { 489 | this.sendItemCrack(p, location, id, data, offsetX, offsetY, offsetZ, speed, count); 490 | } 491 | } 492 | 493 | // BLOCK_DUST 494 | 495 | @Deprecated 496 | public void sendBlockDust(Player p, Location location, int id, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 497 | if (this != BLOCK_DUST) { throw new IllegalArgumentException("This method is only available for BLOCK_DUST!"); } 498 | this.sendToPlayer(p, location, offsetX, offsetY, offsetZ, speed, count, id); 499 | } 500 | 501 | public void sendBlockDust(Player p, Location location, int id, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 502 | if (this != BLOCK_DUST) { throw new IllegalArgumentException("This method is only available for BLOCK_DUST!"); } 503 | this.sendToPlayer(p, location, offsetX, offsetY, offsetZ, speed, count, force, id); 504 | } 505 | 506 | @Deprecated 507 | public void sendBlockDust(Collection players, Location location, int id, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 508 | if (this != BLOCK_DUST) { throw new IllegalArgumentException("This method is only available for BLOCK_DUST!"); } 509 | for (Player p : players) { 510 | this.sendBlockDust(p, location, id, offsetX, offsetY, offsetZ, speed, count); 511 | } 512 | } 513 | 514 | public void sendBlockDust(Collection players, Location location, int id, float offsetX, float offsetY, float offsetZ, float speed, int count, boolean force) throws Exception { 515 | if (this != BLOCK_DUST) { throw new IllegalArgumentException("This method is only available for BLOCK_DUST!"); } 516 | for (Player p : players) { 517 | this.sendBlockDust(p, location, id, offsetX, offsetY, offsetZ, speed, count, force); 518 | } 519 | } 520 | 521 | @Deprecated 522 | public void sendBlockDust(Player[] players, Location location, int id, float offsetX, float offsetY, float offsetZ, float speed, int count) throws Exception { 523 | if (this != BLOCK_DUST) { throw new IllegalArgumentException("This method is only available for BLOCK_DUST!"); } 524 | for (Player p : players) { 525 | this.sendBlockDust(p, location, id, offsetX, offsetY, offsetZ, speed, count); 526 | } 527 | } 528 | 529 | // Reflection 530 | 531 | private static Class nmsPlayerConnection; 532 | private static Class nmsEntityPlayer; 533 | private static Class ioNettyChannel; 534 | private static Method nmsNetworkGetVersion; 535 | 536 | private static Field nmsFieldPlayerConnection; 537 | private static Field nmsFieldNetworkManager; 538 | private static Field nmsFieldNetworkManagerI; 539 | private static Field nmsFieldNetworkManagerM; 540 | 541 | protected static int getVersion(Player p) { 542 | try { 543 | final Object handle = ReflectionUtilities.getHandle(p); 544 | final Object connection = nmsFieldPlayerConnection.get(handle); 545 | final Object network = nmsFieldNetworkManager.get(connection); 546 | final Object channel; 547 | if (ReflectionUtilities.getVersion().contains("1_7")) { 548 | channel = nmsFieldNetworkManagerM.get(network); 549 | } else { 550 | channel = nmsFieldNetworkManagerI.get(network); 551 | } 552 | final Object version = ReflectionUtilities.getVersion().contains("1_7") ? nmsNetworkGetVersion.invoke(network, channel) : 47; 553 | return (int) version; 554 | } catch (Exception e) { 555 | e.printStackTrace(); 556 | } 557 | return 0; 558 | } 559 | 560 | static { 561 | String ver = ReflectionUtilities.getVersion(); 562 | try { 563 | nmsPlayerConnection = ReflectionUtilities.getNMSClass("PlayerConnection"); 564 | nmsEntityPlayer = ReflectionUtilities.getNMSClass("EntityPlayer"); 565 | ioNettyChannel = ver.contains("1_7") ? Class.forName("net.minecraft.util.io.netty.channel.Channel") : Class.forName("io.netty.channel.Channel"); 566 | 567 | nmsFieldPlayerConnection = ReflectionUtilities.getField(nmsEntityPlayer, "playerConnection"); 568 | nmsFieldNetworkManager = ReflectionUtilities.getField(nmsPlayerConnection, "networkManager"); 569 | nmsFieldNetworkManagerI = ReflectionUtilities.getField(nmsFieldNetworkManager.getType(), "i"); 570 | nmsFieldNetworkManagerM = ReflectionUtilities.getField(nmsFieldNetworkManager.getType(), "m"); 571 | 572 | nmsNetworkGetVersion = ReflectionUtilities.getMethod(nmsFieldNetworkManager.getType(), "getVersion", ioNettyChannel); 573 | 574 | } catch (Exception e) { 575 | System.err.println("[ParticleLIB] Error while loading: " + e.getMessage()); 576 | e.printStackTrace(System.err); 577 | Bukkit.getPluginManager().disablePlugin(Bukkit.getPluginManager().getPlugin("ParticleLIB")); 578 | } 579 | } 580 | 581 | @SuppressWarnings({ 582 | "unchecked", 583 | "rawtypes" }) 584 | private static Enum getEnum(String enumFullName) { 585 | String[] x = enumFullName.split("\\.(?=[^\\.]+$)"); 586 | if (x.length == 2) { 587 | String enumClassName = x[0]; 588 | String enumName = x[1]; 589 | try { 590 | Class cl = (Class) Class.forName(enumClassName); 591 | return Enum.valueOf(cl, enumName); 592 | } catch (ClassNotFoundException e) { 593 | e.printStackTrace(); 594 | } 595 | } 596 | return null; 597 | } 598 | 599 | public static boolean isPlayerInRange(Player p, Location center) { 600 | double distance = 0; 601 | if (!p.getLocation().getWorld().equals(center.getWorld())) { return false; } 602 | if ((distance = center.distanceSquared(p.getLocation())) > Double.MAX_VALUE) { return false; } 603 | return distance < particleRange * particleRange; 604 | } 605 | 606 | public static class ReflectionUtilities { 607 | 608 | public static void setValue(Object instance, String fieldName, Object value) throws Exception { 609 | Field field = instance.getClass().getDeclaredField(fieldName); 610 | field.setAccessible(true); 611 | field.set(instance, value); 612 | } 613 | 614 | public static Object getValue(Object instance, String fieldName) throws Exception { 615 | Field field = instance.getClass().getDeclaredField(fieldName); 616 | field.setAccessible(true); 617 | return field.get(instance); 618 | } 619 | 620 | public static String getVersion() { 621 | String name = Bukkit.getServer().getClass().getPackage().getName(); 622 | String version = name.substring(name.lastIndexOf('.') + 1) + "."; 623 | return version; 624 | } 625 | 626 | public static Class getNMSClass(String className) { 627 | String fullName = "net.minecraft.server." + getVersion() + className; 628 | Class clazz = null; 629 | try { 630 | clazz = Class.forName(fullName); 631 | } catch (Exception e) { 632 | e.printStackTrace(); 633 | } 634 | return clazz; 635 | } 636 | 637 | public static Class getOBCClass(String className) { 638 | String fullName = "org.bukkit.craftbukkit." + getVersion() + className; 639 | Class clazz = null; 640 | try { 641 | clazz = Class.forName(fullName); 642 | } catch (Exception e) { 643 | e.printStackTrace(); 644 | } 645 | return clazz; 646 | } 647 | 648 | public static Object getHandle(Object obj) { 649 | try { 650 | return getMethod(obj.getClass(), "getHandle", new Class[0]).invoke(obj, new Object[0]); 651 | } catch (Exception e) { 652 | e.printStackTrace(); 653 | } 654 | return null; 655 | } 656 | 657 | public static Field getField(Class clazz, String name) { 658 | try { 659 | Field field = clazz.getDeclaredField(name); 660 | field.setAccessible(true); 661 | return field; 662 | } catch (Exception e) { 663 | e.printStackTrace(); 664 | } 665 | return null; 666 | } 667 | 668 | public static Method getMethod(Class clazz, String name, Class... args) { 669 | for (Method m : clazz.getMethods()) { 670 | if (m.getName().equals(name) && (args.length == 0 || ClassListEqual(args, m.getParameterTypes()))) { 671 | m.setAccessible(true); 672 | return m; 673 | } 674 | } 675 | return null; 676 | } 677 | 678 | public static boolean ClassListEqual(Class[] l1, Class[] l2) { 679 | boolean equal = true; 680 | if (l1.length != l2.length) { return false; } 681 | for (int i = 0; i < l1.length; i++) { 682 | if (l1[i] != l2[i]) { 683 | equal = false; 684 | break; 685 | } 686 | } 687 | return equal; 688 | } 689 | } 690 | } 691 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/particle/ParticleEffect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 inventivetalent. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package org.inventivetalent.particle; 30 | 31 | import org.bukkit.Color; 32 | import org.bukkit.Location; 33 | import org.bukkit.entity.Player; 34 | import org.bukkit.inventory.ItemStack; 35 | import org.inventivetalent.reflection.minecraft.Minecraft; 36 | import org.inventivetalent.reflection.resolver.ConstructorResolver; 37 | import org.inventivetalent.reflection.resolver.FieldResolver; 38 | import org.inventivetalent.reflection.resolver.MethodResolver; 39 | import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; 40 | 41 | import java.lang.reflect.InvocationTargetException; 42 | import java.util.ArrayList; 43 | import java.util.Collection; 44 | import java.util.Iterator; 45 | 46 | import static org.inventivetalent.reflection.minecraft.Minecraft.Version.*; 47 | 48 | public enum ParticleEffect { 49 | 50 | EXPLOSION_NORMAL("explode"), 51 | EXPLOSION_LARGE("largeexplode"), 52 | EXPLOSION_HUGE("hugeexplosion"), 53 | FIREWORKS_SPARK("fireworksSpark"), 54 | WATER_BUBBLE("bubble"), 55 | WATER_SPLASH("splash"), 56 | WATER_WAKE("wake"), 57 | SUSPENDED("suspended"), 58 | SUSPENDED_DEPTH("depthsuspend"), 59 | CRIT("crit"), 60 | CRIT_MAGIC("magicCrit"), 61 | SMOKE_NORMAL("smoke"), 62 | SMOKE_LARGE("largesmoke"), 63 | SPELL("spell"), 64 | SPELL_INSTANT("instantSpell"), 65 | SPELL_MOB("mobSpell", v1_7_R1, Feature.COLOR), 66 | SPELL_MOB_AMBIENT("mobSpellAmbient"), 67 | SPELL_WITCH("witchMagic"), 68 | DRIP_WATER("dripWater"), 69 | DRIP_LAVA("dripLava"), 70 | VILLAGER_ANGRY("angryVillager"), 71 | VILLAGER_HAPPY("happyVillager"), 72 | TOWN_AURA("townaura"), 73 | NOTE("note", v1_7_R1, Feature.COLOR), 74 | PORTAL("portal"), 75 | ENCHANTMENT_TABLE("enchantmenttable"), 76 | FLAME("flame"), 77 | LAVA("lava"), 78 | FOOTSTEP("footstep"), 79 | CLOUD("cloud"), 80 | REDSTONE("reddust", v1_7_R1, Feature.COLOR), 81 | SNOWBALL("snowballpoof"), 82 | SNOW_SHOVEL("snowshovel"), 83 | SLIME("slime"), 84 | HEART("heart"), 85 | BARRIER("barrier", v1_8_R1), 86 | ITEM_CRACK("iconcrack_", v1_7_R1, Feature.DATA) { 87 | //Unsupported methods 88 | @Override 89 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count) { 90 | throw new ParticleException("Cannot use default send() for ITEM_CRACK"); 91 | } 92 | 93 | @Override 94 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 95 | throw new ParticleException("Cannot use default send() for ITEM_CRACK"); 96 | } 97 | 98 | @Override 99 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 100 | throw new ParticleException("Cannot use default send() for ITEM_CRACK"); 101 | } 102 | 103 | @Override 104 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count) { 105 | throw new ParticleException("Cannot use default send() for ITEM_CRACK"); 106 | } 107 | }, 108 | BLOCK_CRACK("blockcrack_", v1_7_R1, Feature.DATA) { 109 | //Unsupported methods 110 | @Override 111 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count) { 112 | throw new ParticleException("Cannot use default send() for BLOCK_CRACK"); 113 | } 114 | 115 | @Override 116 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 117 | throw new ParticleException("Cannot use default send() for BLOCK_CRACK"); 118 | } 119 | 120 | @Override 121 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 122 | throw new ParticleException("Cannot use default send() for BLOCK_CRACK"); 123 | } 124 | 125 | @Override 126 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count) { 127 | throw new ParticleException("Cannot use default send() for BLOCK_CRACK"); 128 | } 129 | }, 130 | BLOCK_DUST("blockdust_", v1_7_R1, Feature.DATA) { 131 | @Override 132 | public void sendData(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int itemId, byte data) { 133 | data = -1; 134 | super.sendData(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, itemId, data); 135 | } 136 | 137 | //Unsupported methods 138 | @Override 139 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count) { 140 | throw new ParticleException("Cannot use default send() for BLOCK_DUST"); 141 | } 142 | 143 | @Override 144 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 145 | throw new ParticleException("Cannot use default send() for BLOCK_DUST"); 146 | } 147 | 148 | @Override 149 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 150 | throw new ParticleException("Cannot use default send() for BLOCK_DUST"); 151 | } 152 | 153 | @Override 154 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count) { 155 | throw new ParticleException("Cannot use default send() for BLOCK_DUST"); 156 | } 157 | }, 158 | WATER_DROP("droplet", v1_8_R1), 159 | ITEM_TAKE("take", v1_8_R1), 160 | MOB_APPEARANCE("mobappearance", v1_8_R1), 161 | DRAGON_BREATH("dragonbreath", v1_9_R1), 162 | END_ROD("endRod", v1_9_R1), 163 | DAMAGE_INDICATOR("damageIndicator", v1_9_R1), 164 | SWEEP_ATTACK("sweepAttack", v1_9_R1), 165 | FALLING_DUST("fallingDust", v1_10_R1), 166 | SPIT("spit", v1_11_R1), 167 | TOTEM("totem", v1_11_R1); 168 | 169 | private String name; 170 | private Minecraft.Version minVersion; 171 | private Feature feature; 172 | protected Particle particle; 173 | 174 | ParticleEffect(String name, Minecraft.Version minVersion, Feature feature) { 175 | this.name = name; 176 | this.minVersion = minVersion; 177 | this.feature = feature; 178 | if (feature != null) { 179 | this.particle = feature.particle(this); 180 | } else { 181 | this.particle = new Particle(this); 182 | } 183 | } 184 | 185 | ParticleEffect(String name, Minecraft.Version minVersion) { 186 | this(name, minVersion, null); 187 | } 188 | 189 | ParticleEffect(String name) { 190 | this(name, v1_7_R1); 191 | } 192 | 193 | public String getName() { 194 | return name; 195 | } 196 | 197 | /** 198 | * @return the minimum {@link org.inventivetalent.reflection.minecraft.Minecraft.Version} required for this particle 199 | */ 200 | public Minecraft.Version getMinVersion() { 201 | return minVersion; 202 | } 203 | 204 | /** 205 | * @return true if this particle is compatible with the server version 206 | */ 207 | public boolean isCompatible() { 208 | return Minecraft.VERSION.newerThan(getMinVersion()); 209 | } 210 | 211 | /** 212 | * Check if this particle has special {@link org.inventivetalent.particle.ParticleEffect.Feature}s - Particles with features cannot use the default send() methods - Particles without features cannot use special sendColor or sendData methods 213 | * 214 | * @param feature {@link org.inventivetalent.particle.ParticleEffect.Feature} to check 215 | * @return true if this particle has the feature 216 | * @see #hasNoFeatures() 217 | */ 218 | public boolean hasFeature(Feature feature) { 219 | return this.feature == feature; 220 | } 221 | 222 | /** 223 | * Check if this particle has no {@link org.inventivetalent.particle.ParticleEffect.Feature}s - Particles without features cannot use special sendColor or sendData methods - Particles with features cannot use the default send() methods 224 | * 225 | * @return true if this particle has no special features 226 | * @see #hasFeature(Feature) 227 | */ 228 | public boolean hasNoFeatures() { 229 | return this.feature == null; 230 | } 231 | 232 | //*** Public send methods 233 | 234 | //General 235 | 236 | /** 237 | * Send this particle 238 | * 239 | * @param receivers Collection of receivers 240 | * @param x X-Location 241 | * @param y Y-Location 242 | * @param z Z-Location 243 | * @param offsetX X-Offset 244 | * @param offsetY Y-Offset 245 | * @param offsetZ Z-Offset 246 | * @param speed Particle speed 247 | * @param count Particle count 248 | * @param range Maximum visibility range 249 | */ 250 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 251 | double squareRange = range * range; 252 | Collection inRange = new ArrayList<>(); 253 | for (Player receiver : receivers) { 254 | if (receiver.getLocation().distanceSquared(new Location(receiver.getWorld(), x, y, z)) <= squareRange) { inRange.add(receiver); } 255 | } 256 | this.send(inRange, x, y, z, offsetX, offsetY, offsetZ, speed, count); 257 | } 258 | 259 | /** 260 | * Send this particle 261 | * 262 | * @param receivers Collection of receivers 263 | * @param location {@link Location} 264 | * @param offsetX X-Offset 265 | * @param offsetY Y-Offset 266 | * @param offsetZ Z-Offset 267 | * @param speed Particle speed 268 | * @param count Particle count 269 | * @param range Maximum visibility range 270 | */ 271 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count, double range) { 272 | receivers = new ArrayList<>(receivers); 273 | for (Iterator iterator = receivers.iterator(); iterator.hasNext(); ) { 274 | if (!iterator.next().getWorld().getName().equals(location.getWorld().getName())) { iterator.remove(); } 275 | } 276 | send(receivers, location.getX(), location.getY(), location.getZ(), offsetX, offsetY, offsetZ, speed, count, range); 277 | } 278 | 279 | /** 280 | * Send this particle 281 | * 282 | * @param receivers Collection of receivers 283 | * @param x X-Location 284 | * @param y Y-Location 285 | * @param z Z-Location 286 | * @param offsetX X-Offset 287 | * @param offsetY Y-Offset 288 | * @param offsetZ Z-Offset 289 | * @param speed Particle speed 290 | * @param count Particle count 291 | */ 292 | public void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count) { 293 | this.particle.send(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count); 294 | } 295 | 296 | /** 297 | * Send this particle 298 | * 299 | * @param receivers Collection of receivers 300 | * @param location {@link Location} 301 | * @param offsetX X-Offset 302 | * @param offsetY Y-Offset 303 | * @param offsetZ Z-Offset 304 | * @param speed Particle speed 305 | * @param count Particle count 306 | */ 307 | public void send(Collection receivers, Location location, double offsetX, double offsetY, double offsetZ, double speed, int count) { 308 | receivers = new ArrayList<>(receivers); 309 | for (Iterator iterator = receivers.iterator(); iterator.hasNext(); ) { 310 | if (!iterator.next().getWorld().getName().equals(location.getWorld().getName())) { iterator.remove(); } 311 | } 312 | this.particle.send(receivers, location.getX(), location.getY(), location.getZ(), offsetX, offsetY, offsetZ, speed, count); 313 | } 314 | 315 | //Color 316 | 317 | /** 318 | * Send this particle with a color 319 | * 320 | * @param receivers Collection of receivers 321 | * @param x X-Location 322 | * @param y Y-Location 323 | * @param z Z-Location 324 | * @param color {@link org.bukkit.Color} of the particle 325 | * @throws ParticleException if this particle cannot have a color 326 | */ 327 | public void sendColor(Collection receivers, double x, double y, double z, org.bukkit.Color color) { 328 | if (!hasFeature(Feature.COLOR)) { throw new ParticleException("This particle cannot be colored"); } 329 | ((ColoredParticle) this.particle).send(receivers, x, y, z, color); 330 | } 331 | 332 | /** 333 | * Send this particle with a color 334 | * 335 | * @param receivers Collection of receivers 336 | * @param x X-Location 337 | * @param y Y-Location 338 | * @param z Z-Location 339 | * @param color {@link java.awt.Color} of the particle 340 | * @throws ParticleException if this particle cannot have a color 341 | */ 342 | public void sendColor(Collection receivers, double x, double y, double z, java.awt.Color color) { 343 | if (!hasFeature(Feature.COLOR)) { throw new ParticleException("This particle cannot be colored"); } 344 | ((ColoredParticle) this.particle).send(receivers, x, y, z, color); 345 | } 346 | 347 | public void sendColor(Collection receivers, Location location, Color color) { 348 | receivers = new ArrayList<>(receivers); 349 | for (Iterator iterator = receivers.iterator(); iterator.hasNext(); ) { 350 | if (!iterator.next().getWorld().getName().equals(location.getWorld().getName())) { iterator.remove(); } 351 | } 352 | sendColor(receivers, location.getX(), location.getY(), location.getZ(), color); 353 | } 354 | 355 | public void sendColor(Collection receivers, Location location, java.awt.Color color) { 356 | receivers = new ArrayList<>(receivers); 357 | for (Iterator iterator = receivers.iterator(); iterator.hasNext(); ) { 358 | if (!iterator.next().getWorld().getName().equals(location.getWorld().getName())) { iterator.remove(); } 359 | } 360 | sendColor(receivers, location.getX(), location.getY(), location.getZ(), color); 361 | } 362 | 363 | //Data 364 | 365 | /** 366 | * Send this particle with block or item data 367 | * 368 | * @param receivers Collection of receivers 369 | * @param x X-Location 370 | * @param y Y-Location 371 | * @param z Z-Location 372 | * @param offsetX X-Offset 373 | * @param offsetY Y-Offset 374 | * @param offsetZ Z-Offset 375 | * @param speed Particle speed 376 | * @param count Particle count 377 | * @param itemId ID of the item/block 378 | * @param data Data of the item/block 379 | * @throws ParticleException if this particle cannot have block or item data 380 | */ 381 | public void sendData(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int itemId, byte data) { 382 | if (!hasFeature(Feature.DATA)) { throw new ParticleException("This particle cannot have block/item data"); } 383 | ((DataParticle) this.particle).send(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, itemId, data); 384 | } 385 | 386 | /** 387 | * Send this particle with block or item data 388 | * 389 | * @param receivers Collection of receivers 390 | * @param x X-Location 391 | * @param y Y-Location 392 | * @param z Z-Location 393 | * @param offsetX X-Offset 394 | * @param offsetY Y-Offset 395 | * @param offsetZ Z-Offset 396 | * @param speed Particle speed 397 | * @param count Particle count 398 | * @param itemStack {@link ItemStack} containing the ID&data of the item/block 399 | * @throws ParticleException if this particle cannot have block or item data 400 | */ 401 | public void sendData(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, ItemStack itemStack) { 402 | sendData(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, itemStack.getTypeId(), itemStack.getData().getData()); 403 | } 404 | 405 | public enum Feature { 406 | /** 407 | * The particle can be colore 408 | */ 409 | COLOR { 410 | @Override 411 | Particle particle(ParticleEffect effect) { 412 | return new ColoredParticle(effect); 413 | } 414 | }, 415 | /** 416 | * The particle can have item or block data 417 | */ 418 | DATA { 419 | @Override 420 | Particle particle(ParticleEffect effect) { 421 | return new DataParticle(effect); 422 | } 423 | }; 424 | 425 | Feature() { 426 | } 427 | 428 | public boolean applies(ParticleEffect particleEffect) { 429 | return particleEffect.hasFeature(this); 430 | } 431 | 432 | Particle particle(ParticleEffect effect) { 433 | return new Particle(effect); 434 | } 435 | 436 | } 437 | 438 | static class Particle { 439 | final Class PacketParticle = Reflection.NMS_CLASS_RESOLVER.resolveSilent("PacketPlayOutWorldParticles"); 440 | final Class EnumParticle = Reflection.NMS_CLASS_RESOLVER.resolveSilent("EnumParticle"); 441 | final ConstructorResolver PacketParticleConstructorResolver = new ConstructorResolver(PacketParticle); 442 | 443 | ParticleEffect effect; 444 | 445 | Particle(ParticleEffect particleEffect) { 446 | this.effect = particleEffect; 447 | } 448 | 449 | void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int... extra) { 450 | if (!this.effect.isCompatible()) { throw new ParticleException("Particle " + this.effect + " is not compatible with the server version (" + Minecraft.VERSION + " < " + this.effect.getMinVersion() + ")"); } 451 | try { 452 | if (Minecraft.VERSION.newerThan(v1_8_R1)) { 453 | send_1_8(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, extra); 454 | } else {//1.7 455 | send_1_7(effect.name, receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count); 456 | } 457 | } catch (Exception e) { 458 | throw new ParticleException("Failed to send particle " + this.effect + " to " + receivers.toString(), e); 459 | } 460 | } 461 | 462 | void send_1_8(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int... extra) throws Exception { 463 | Object packet = PacketParticleConstructorResolver.resolve(new Class[] { 464 | EnumParticle, 465 | boolean.class, 466 | float.class, 467 | float.class, 468 | float.class, 469 | float.class, 470 | float.class, 471 | float.class, 472 | float.class, 473 | int.class, 474 | int[].class }).newInstance(// 475 | getEnum(EnumParticle.getName() + "." + effect.name()),//particle enum 476 | true,//long-distance 477 | (float) x,//X 478 | (float) y,//Y 479 | (float) z,//Z 480 | (float) offsetX,//X-offset 481 | (float) offsetY,//Y-offset 482 | (float) offsetZ,//Z-offset 483 | (float) speed,//Speed 484 | count,//Count 485 | extra//Extra 486 | ); 487 | 488 | for (Player receiver : receivers) { 489 | sendPacket(packet, receiver); 490 | } 491 | } 492 | 493 | void send_1_7(String name, Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count) throws Exception { 494 | Object packet = PacketParticleConstructorResolver.resolve(new Class[] { 495 | String.class, 496 | float.class, 497 | float.class, 498 | float.class, 499 | float.class, 500 | float.class, 501 | float.class, 502 | float.class, 503 | int.class }).newInstance(// 504 | name,//Particle name 505 | (float) x,//X 506 | (float) y,//Y 507 | (float) z, //Z 508 | (float) offsetX,//X 509 | (float) offsetY,//Y 510 | (float) offsetZ,//Z 511 | (float) speed,//Speed 512 | count//Count 513 | ); 514 | 515 | for (Player receiver : receivers) { 516 | sendPacket(packet, receiver); 517 | } 518 | } 519 | } 520 | 521 | static class ColoredParticle extends Particle { 522 | 523 | ColoredParticle(ParticleEffect particleEffect) { 524 | super(particleEffect); 525 | } 526 | 527 | void send(Collection receivers, double x, double y, double z, org.bukkit.Color color) { 528 | send(receivers, x, y, z, getColor(color.getRed()), getColor(color.getGreen()), getColor(color.getBlue()), 1, 0); 529 | } 530 | 531 | void send(Collection receivers, double x, double y, double z, java.awt.Color color) { 532 | send(receivers, x, y, z, getColor(color.getRed()), getColor(color.getGreen()), getColor(color.getBlue()), 1, 0); 533 | } 534 | 535 | @Override 536 | void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int... extra) { 537 | super.send(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, extra); 538 | } 539 | 540 | double getColor(double value) { 541 | if (value <= 0) { 542 | value = -1; 543 | } 544 | return value / 255; 545 | } 546 | } 547 | 548 | static class DataParticle extends Particle { 549 | 550 | DataParticle(ParticleEffect particleEffect) { 551 | super(particleEffect); 552 | } 553 | 554 | void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int id, byte data) { 555 | try { 556 | if (Minecraft.VERSION.newerThan(v1_8_R1)) { 557 | send_1_8(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, new int[] { 558 | id, 559 | id | data << 12 }); 560 | } else { 561 | send_1_7(effect.name + id + (data >= 0 ? "_" + data : ""), receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count); 562 | } 563 | } catch (Exception e) { 564 | throw new ParticleException("Failed to send particle " + this.effect + " to " + receivers.toString(), e); 565 | } 566 | } 567 | 568 | @Override 569 | void send(Collection receivers, double x, double y, double z, double offsetX, double offsetY, double offsetZ, double speed, int count, int... extra) { 570 | super.send(receivers, x, y, z, offsetX, offsetY, offsetZ, speed, count, extra); 571 | } 572 | } 573 | 574 | static Enum getEnum(String enumFullName) { 575 | String[] x = enumFullName.split("\\.(?=[^\\.]+$)"); 576 | if (x.length == 2) { 577 | String enumClassName = x[0]; 578 | String enumName = x[1]; 579 | try { 580 | Class cl = (Class) Class.forName(enumClassName); 581 | return Enum.valueOf(cl, enumName); 582 | } catch (ClassNotFoundException e) { 583 | e.printStackTrace(); 584 | } 585 | } 586 | return null; 587 | } 588 | 589 | static class Reflection { 590 | 591 | static final NMSClassResolver NMS_CLASS_RESOLVER = new NMSClassResolver(); 592 | //Packets 593 | private static FieldResolver EntityPlayerFieldResolver; 594 | private static MethodResolver PlayerConnectionMethodResolver; 595 | } 596 | 597 | protected static void sendPacket(Object packet, Player p) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { 598 | if (Reflection.EntityPlayerFieldResolver == null) { 599 | Reflection.EntityPlayerFieldResolver = new FieldResolver(Reflection.NMS_CLASS_RESOLVER.resolve("EntityPlayer")); 600 | } 601 | if (Reflection.PlayerConnectionMethodResolver == null) { 602 | Reflection.PlayerConnectionMethodResolver = new MethodResolver(Reflection.NMS_CLASS_RESOLVER.resolve("PlayerConnection")); 603 | } 604 | 605 | try { 606 | Object handle = Minecraft.getHandle(p); 607 | final Object connection = Reflection.EntityPlayerFieldResolver.resolve("playerConnection").get(handle); 608 | Reflection.PlayerConnectionMethodResolver.resolve("sendPacket").invoke(connection, packet); 609 | } catch (ReflectiveOperationException e) { 610 | throw new RuntimeException(e); 611 | } 612 | } 613 | 614 | } 615 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/particle/ParticleException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 inventivetalent. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package org.inventivetalent.particle; 30 | 31 | public class ParticleException extends RuntimeException { 32 | public ParticleException() { 33 | } 34 | 35 | public ParticleException(String message) { 36 | super(message); 37 | } 38 | 39 | public ParticleException(String message, Throwable cause) { 40 | super(message, cause); 41 | } 42 | 43 | public ParticleException(Throwable cause) { 44 | super(cause); 45 | } 46 | 47 | public ParticleException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 48 | super(message, cause, enableSuppression, writableStackTrace); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/particle/ParticlePlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 inventivetalent. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package org.inventivetalent.particle; 30 | 31 | import org.bukkit.event.Listener; 32 | import org.bukkit.plugin.java.JavaPlugin; 33 | 34 | public class ParticlePlugin extends JavaPlugin implements Listener { 35 | 36 | @Override 37 | public void onEnable() { 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ParticleLIB 2 | main: org.inventivetalent.particle.ParticlePlugin 3 | version: ${project.version} 4 | author: inventivetalent --------------------------------------------------------------------------------