2016/07/02: 色指定が出来るようにした 1.10きてた
2016/03/07: 全部変更 1.9きた
2015/07/06: EnumParticle setItemIDandData において、BlockIDがちゃんと反映されない問題の修正
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Method; | |
import java.util.HashMap; | |
import java.util.Map; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Color; | |
import org.bukkit.Location; | |
import org.bukkit.Material; | |
import org.bukkit.block.Block; | |
import org.bukkit.entity.Player; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.material.MaterialData; | |
public class ParticleAPI { | |
public static class Particle { | |
private EnumParticle particle; | |
private Location location; | |
private float[] diffusion; | |
private float speed; | |
private int amount; | |
private boolean force; | |
private Object ParticlesPacket; | |
/** | |
* 新しいParticleのインスタンスを生成します。 | |
* @param particle パーティクルの種類 | |
* @param location 発生位置 | |
* @param offsetX x方向への拡散度 | |
* @param offsetY y方向への拡散度 | |
* @param offsetZ z方向への拡散度 | |
* @param speed 速度 | |
*/ | |
public Particle(EnumParticle particle, Location location, float offsetX, float offsetY, float offsetZ, float speed) { | |
this(particle, location, offsetX, offsetY, offsetZ, speed, 1, false); | |
} | |
/** | |
* 新しいParticleのインスタンスを生成します。 | |
* @param particle パーティクルの種類 | |
* @param location 発生位置 | |
* @param offsetX x方向への拡散度 | |
* @param offsetY y方向への拡散度 | |
* @param offsetZ z方向への拡散度 | |
* @param speed 速度 | |
* @param amount 量 | |
*/ | |
public Particle(EnumParticle particle, Location location, float offsetX, float offsetY, float offsetZ, float speed, int amount) { | |
this(particle, location, offsetX, offsetY, offsetZ, speed, amount, false); | |
} | |
/** | |
* 新しいParticleのインスタンスを生成します。 | |
* @param particle パーティクルの種類 | |
* @param location 発生位置 | |
* @param offsetX x方向への拡散度 | |
* @param offsetY y方向への拡散度 | |
* @param offsetZ z方向への拡散度 | |
* @param speed 速度 | |
* @param amount 量 | |
* @param force 強制描写 | |
*/ | |
public Particle(EnumParticle particle, Location location, float offsetX, float offsetY, float offsetZ, float speed, int amount, boolean force) { | |
this.particle = particle; | |
float[] color = this.particle.getColor(); | |
this.location = location; | |
this.diffusion = color != null ? color : new float[]{offsetX, offsetY, offsetZ}; | |
this.speed = color != null ? 1f : speed; | |
this.amount = color != null ? 0 : amount; | |
this.force = force; | |
if(is1_7 || is1_8) // 1.9以降ではBukkitの提供するAPIを使うようにしています。もしそれが嫌ならここの条件式だけを除いてください。 | |
convertPacket(); | |
} | |
private void convertPacket() { | |
try { | |
if(is1_7) { | |
Class<?> a = getNMSClass("PacketPlayOutWorldParticles"); | |
Constructor<?> b = a.getConstructor(new Class<?>[]{String.class, float.class, float.class, float.class, float.class, float.class, float.class, float.class, int.class}); | |
ParticlesPacket = b.newInstance(new Object[]{getEnumParticle(particle), (float)location.getX(), (float)location.getY(), (float)location.getZ(), diffusion[0], diffusion[1], diffusion[2], speed, amount}); | |
} else { | |
Class<?> a = getNMSClass("EnumParticle"); | |
Class<?> b = getNMSClass("PacketPlayOutWorldParticles"); | |
Constructor<?> c = b.getConstructor(new Class<?>[]{a, boolean.class, float.class, float.class, float.class, float.class, float.class, float.class, float.class, int.class, int[].class}); | |
ParticlesPacket = c.newInstance(new Object[]{getEnumParticle(particle), force, (float)location.getX(), (float)location.getY(), (float)location.getZ(), diffusion[0], diffusion[1], diffusion[2], speed, amount, particle.getRawData()}); | |
} | |
} catch(Throwable ex) { | |
ex.printStackTrace(); | |
ParticlesPacket = null; | |
} | |
} | |
/** | |
* 指定したPlayerに向けてパーティクルを発生させます | |
* @param player パーティクルを描写させたいPlayer | |
* @return 正常に実行されればtrue、そうでなければfalseを返します | |
*/ | |
public boolean sendParticle(Player player) { | |
try { | |
if(ParticlesPacket != null) { | |
sendPacket(player, ParticlesPacket); | |
} else { | |
player.spawnParticle(org.bukkit.Particle.valueOf(particle.name()), location, amount, diffusion[0], diffusion[1], diffusion[2], speed, particle.getData()); | |
} | |
return true; | |
} catch(Throwable ex) { | |
ex.printStackTrace(); | |
} | |
return false; | |
} | |
} | |
/** | |
* パーティクルの種類を表す列挙型 | |
*/ | |
public static enum EnumParticle { | |
EXPLOSION_NORMAL("explode", 0), | |
EXPLOSION_LARGE("largeexplode", 1), | |
EXPLOSION_HUGE("hugeexplosion", 2), | |
FIREWORKS_SPARK("fireworksSpark", 3), | |
WATER_BUBBLE("bubble", 4), | |
WATER_SPLASH("splash", 5), | |
WATER_WAKE("wake", 6), | |
SUSPENDED("suspended", 7), | |
SUSPENDED_DEPTH("depthsuspend", 8), | |
CRIT("crit", 9), | |
CRIT_MAGIC("magicCrit", 10), | |
SMOKE_NORMAL("smoke", 11), | |
SMOKE_LARGE("largesmoke", 12), | |
SPELL("spell", 13), | |
SPELL_INSTANT("instantSpell", 14), | |
SPELL_MOB("mobSpell", 15, DataType.Color), | |
SPELL_MOB_AMBIENT("mobSpellAmbient", 16, DataType.Color), | |
SPELL_WITCH("witchMagic", 17), | |
DRIP_WATER("dripWater", 18), | |
DRIP_LAVA("dripLava", 19), | |
VILLAGER_ANGRY("angryVillager", 20), | |
VILLAGER_HAPPY("happyVillager", 21), | |
TOWN_AURA("townaura", 22), | |
NOTE("note", 23, DataType.Note), | |
PORTAL("portal", 24), | |
ENCHANTMENT_TABLE("enchantmenttable", 25), | |
FLAME("flame", 26), | |
LAVA("lava", 27), | |
FOOTSTEP("footstep", 28), | |
CLOUD("cloud", 29), | |
REDSTONE("reddust", 30, DataType.Color), | |
SNOWBALL("snowballpoof", 31), | |
SNOW_SHOVEL("snowshovel", 32), | |
SLIME("slime", 33), | |
HEART("heart", 34), | |
BARRIER("barrier", 35), | |
ITEM_CRACK("iconcrack", 36, DataType.ItemStack), | |
BLOCK_CRACK("blockcrack", 37, DataType.Block), | |
BLOCK_DUST("blockdust", 38, DataType.Block), | |
WATER_DROP("droplet", 39), | |
ITEM_TAKE("take", 40), | |
MOB_APPEARANCE("mobappearance",41), | |
DRAGON_BREATH("dragonbreath", 42), | |
END_ROD("endRod", 43), | |
DAMAGE_INDICATOR("damageIndicator", 44), | |
SWEEP_ATTACK("sweepAttack", 45), | |
FALLING_DUST("fallingdust", 46, DataType.Block), | |
; | |
private String name; | |
private int id; | |
private DataType type; | |
private int[] numData = null; | |
private ItemStack itemData = new ItemStack(Material.IRON_SPADE); | |
private MaterialData materialData = new MaterialData(Material.STONE); | |
private Color color = null; | |
private int pitch = -1; | |
private static Map<String, EnumParticle> X; | |
private static Map<Integer, EnumParticle> Y; | |
static { | |
X = new HashMap<String, EnumParticle>(); | |
Y = new HashMap<Integer, EnumParticle>(); | |
for(EnumParticle a : values()){ | |
X.put(a.name, a); | |
Y.put(a.id, a); | |
} | |
} | |
private EnumParticle(String name, int id) { | |
this(name, id, DataType.None); | |
} | |
private EnumParticle(String name, int id, DataType type) { | |
this.name = name; | |
this.id = id; | |
this.type = type; | |
switch(this.type) { | |
case ItemStack: | |
numData = new int[]{256, 0}; | |
break; | |
case Block: | |
numData = new int[]{1, 0}; | |
break; | |
default: break; | |
} | |
} | |
/** | |
* パーティクルの名前を返します。<br> | |
* ここでの名前はparticleコマンドで指定する名前です。 | |
* @return パーティクルの名前を返します | |
*/ | |
public String getName() { | |
if(hasDataParticle() && is1_7) { | |
return name+"_"+numData[0]+"_"+numData[1]; | |
} else return name; | |
} | |
/** | |
* パーティクルの数値IDを返します。 | |
* @return パーティクルの数値IDを返します | |
*/ | |
public int getID() { | |
return id; | |
} | |
/** | |
* このパーティクルが、1.8で追加された新しいパーティクルであるかを返します | |
* @return このパーティクルが1.8で追加されたものならtrue、そうでなければfalseを返します | |
*/ | |
public boolean is1_8NewParticle() { | |
switch (this) { | |
case BARRIER: | |
case WATER_DROP: | |
case ITEM_TAKE: | |
case MOB_APPEARANCE: return true; | |
default: return false; | |
} | |
} | |
/** | |
* このパーティクルが、1.9で追加された新しいパーティクルであるかを返します | |
* @return このパーティクルが1.9で追加されたものならtrue、そうでなければfalseを返します | |
*/ | |
public boolean is1_9NewParticle() { | |
switch (this) { | |
case DRAGON_BREATH: | |
case END_ROD: | |
case DAMAGE_INDICATOR: | |
case SWEEP_ATTACK: return true; | |
default: return false; | |
} | |
} | |
/** | |
* このパーティクルが、1.10で追加された新しいパーティクルであるかを返します | |
* @return このパーティクルが1.10で追加されたものならtrue、そうでなければfalseを返します | |
*/ | |
public boolean is1_10NewParticle() { | |
return this == EnumParticle.FALLING_DUST; | |
} | |
/** | |
* このパーティクルが、データ値を持つパーティクルであるかを返します。 | |
* @return このパーティクルがデータ値を持てばtrue、そうでなければfalseを返します | |
*/ | |
public boolean hasDataParticle() { | |
switch (this.type) { | |
case ItemStack: | |
case Block: return true; | |
default: return false; | |
} | |
} | |
/** | |
* このパーティクルのデータ値に、引数のItemStackの情報を割り当てます。 | |
* @param item 適用させたい情報を持つItemStack | |
* @return データ値変更後のEnumParticle | |
*/ | |
@SuppressWarnings("deprecation") | |
public EnumParticle setItemData(ItemStack item) { | |
if(item == null) throw new NullPointerException("`item` is null."); | |
if(type == DataType.ItemStack) { | |
itemData = item; | |
numData[0] = item.getTypeId(); | |
numData[1] = (int)item.getDurability(); | |
} | |
return this; | |
} | |
/** | |
* このパーティクルのデータ値に、引数のBlockの情報を割り当てます。 | |
* @param block 適用させたい情報を持つBlock | |
* @return データ値変更後のEnumParticle | |
*/ | |
@SuppressWarnings("deprecation") | |
public EnumParticle setBlockData(Block block) { | |
if(block == null) throw new NullPointerException("`block` is null."); | |
switch (this.type) { | |
case ItemStack: | |
return setItemData(new ItemStack(block.getType(), 1, (short)block.getData())); | |
case Block: | |
materialData = block.getState().getData(); | |
numData[0] = block.getTypeId(); | |
numData[1] = (int)block.getData(); | |
default: return this; | |
} | |
} | |
/** | |
* このパーティクルのデータ値に、引数のMaterialの情報を割り当てます。 | |
* @param material 適用させたい情報を持つMaterial | |
* @return データ値変更後のEnumParticle | |
*/ | |
@SuppressWarnings("deprecation") | |
public EnumParticle setMaterialData(Material material) { | |
if(material == null) throw new NullPointerException("`material` is null."); | |
switch (this.type) { | |
case ItemStack: | |
return setItemData(new ItemStack(material)); | |
case Block: | |
materialData = new MaterialData(material); | |
numData[0] = material.getId(); | |
numData[1] = 0; | |
default: return this; | |
} | |
} | |
/** | |
* このパーティクルのデータ値を設定します。 | |
* @param id 設定したいアイテムID | |
* @return データ値変更後のEnumParticle | |
*/ | |
public EnumParticle setNumberData(int id) { | |
return setNumberData(id, 0); | |
} | |
/** | |
* このパーティクルのデータ値を設定します。 | |
* @param id 設定したいアイテムID | |
* @param data 設定したいダメージ値 | |
* @return データ値変更後のEnumParticle | |
*/ | |
@SuppressWarnings({ "deprecation", "unused" }) | |
public EnumParticle setNumberData(int id, int data) { | |
switch (this.type) { | |
case ItemStack: | |
return setItemData(new ItemStack(id, 1, (short)data)); | |
case Block: | |
MaterialData md = new MaterialData(id, (byte)data); | |
if(md == null) throw new NullPointerException("`MaterialData` is null."); | |
materialData = md; | |
numData[0] = id; | |
numData[1] = data; | |
default: return this; | |
} | |
} | |
/** | |
* 1.9以降用のデータ | |
* @return ItemStackやMaterialData | |
*/ | |
private Object getData() { | |
switch (this.type) { | |
case ItemStack: return itemData; | |
case Block: return materialData; | |
default: return null; | |
} | |
} | |
/** | |
* 数値データ | |
* @return ItemIDとDamage値の配列 | |
*/ | |
private int[] getRawData() { | |
switch (this.type) { | |
case ItemStack: return numData; | |
case Block: return new int[]{ numData[1]*4096 + numData[0] }; | |
default: return null; | |
} | |
} | |
/** | |
* 一部のパーティクルの色を指定します。 | |
* <p>色の指定が出来るのは{@link EnumParticle.SPELL_MOB}("mobSpell")や{@link EnumParticle.REDSTONE}("reddust")といった一部のパーティクルだけです。<br> | |
* 色を指定すると、拡散度と速度、発生するパーティクルが1回に固定されます。</p> | |
* @param color 指定する色 | |
* @return EnumParticle | |
*/ | |
public EnumParticle setColor(Color color) { | |
if(color == null) throw new NullPointerException("`color` is null."); | |
if(this.type == DataType.Color) this.color = color; | |
return this; | |
} | |
/** | |
* {@link EnumParticle.NOTE}のパーティクルの色を指定します。 | |
* <p>{@link EnumParticle.NOTE}("note")専用です。<br> | |
* 色を指定すると、拡散度と速度、発生するパーティクルが1回に固定されます。<br> | |
* 値は0以上24以下で指定されます。</p> | |
* @param pitch 指定する色 | |
* @return EnumParticle | |
*/ | |
public EnumParticle setPitch(int pitch) { | |
if(this.type == DataType.Note && pitch >= 0 && pitch <= 24) this.pitch = pitch; | |
return this; | |
} | |
/** | |
* 色の設定を適用させるためのもの | |
* @return 設定がないならnull | |
*/ | |
private float[] getColor() { | |
switch (this.type) { | |
case Color: | |
if(color == null) return null; | |
float[] colorData = new float[]{color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f}; | |
if(this == EnumParticle.REDSTONE) colorData[0] -= 1f; | |
return colorData; | |
case Note: | |
if(pitch < 0) return null; | |
return new float[]{pitch/24f, 0f, 0f}; | |
default: return null; | |
} | |
} | |
/** | |
* 文字列から該当するパーティクルの種類を返します | |
* @param name パーティクルの名前 | |
* @return 見つからない場合はnullが返されます | |
*/ | |
public static EnumParticle getParticle(String name) { | |
String[] divi = name.split("_"); | |
int len = divi.length; | |
if(len == 2 || len == 3) { | |
EnumParticle par = getParticle(divi[0]); | |
if(par != null && par.hasDataParticle()) { | |
int id = 1; | |
int data = 0; | |
try { | |
if(len == 2) { | |
int num = Integer.parseInt(divi[1]); | |
if(num >= 4096) { | |
id = num / 4096; | |
data = num % 4096; | |
} else { | |
id = num; | |
} | |
} else if(len == 3) { | |
id = Integer.parseInt(divi[1]); | |
data = Integer.parseInt(divi[2]); | |
} | |
} catch(ArrayIndexOutOfBoundsException | NumberFormatException e) {} | |
par.setNumberData(id, data); | |
} | |
return par; | |
} | |
return X.containsKey(name) ? X.get(name) : null; | |
} | |
/** | |
* 数値IDから該当するパーティクルの種類を返します | |
* @param id パーティクルの数値ID | |
* @return 見つからない場合はnullが返されます | |
*/ | |
public static EnumParticle getParticle(int id) { | |
return Y.containsKey(id) ? Y.get(id) : null; | |
} | |
} | |
private static String version; | |
private static String NMSPackageName; | |
private static boolean is1_7 = false; | |
private static boolean is1_8 = false; | |
//private static boolean is1_9 = false; | |
static { | |
version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; | |
NMSPackageName = "net.minecraft.server."+version; | |
is1_7 = version.startsWith("v1_7_R"); | |
is1_8 = version.startsWith("v1_8_R"); | |
//is1_9 = version.startsWith("v1_9_R"); | |
} | |
private static Object getEnumParticle(EnumParticle par) throws Exception { | |
if(is1_7) { | |
return par.getName(); | |
} else { | |
int i = par.getID(); | |
Class<?> EnumParticleClass = getNMSClass("EnumParticle"); | |
Method a = EnumParticleClass.getDeclaredMethod("a", int.class); | |
Object EnumParticle = a.invoke(EnumParticleClass, i); | |
return EnumParticle; | |
} | |
} | |
private static void sendPacket(Player player, Object packet) throws Exception { | |
Method PlayerHandle = player.getClass().getMethod("getHandle"); | |
Object EntityPlayer = PlayerHandle.invoke(player); | |
Object PlayerConnection = EntityPlayer.getClass().getField("playerConnection").get(EntityPlayer); | |
Method sendPacket = null; | |
for(Method m : PlayerConnection.getClass().getDeclaredMethods()){ | |
if(m.getName().equals("sendPacket")){ | |
sendPacket = m; | |
break; | |
} | |
} | |
sendPacket.invoke(PlayerConnection, packet); | |
} | |
private static Class<?> getNMSClass(String name) throws Exception { | |
Class<?> craftclass = Class.forName(NMSPackageName+"."+name); | |
return craftclass; | |
} | |
private static enum DataType { | |
ItemStack, | |
Block, | |
Color, | |
Note, | |
None | |
} | |
} |
@EventHandler | |
public void onPlayerInteract(PlayerInteractEvent event) { | |
Player player = event.getPlayer(); | |
ItemStack item = player.getInventory().getItemInMainHand(); | |
ItemStack sub = player.getInventory().getItemInOffHand(); | |
Action act = event.getAction(); | |
if(item == null) return; | |
Location eye = player.getEyeLocation().add(player.getEyeLocation().getDirection().multiply(2)); | |
switch(item.getType()) { | |
/* 白のレコードで、空中右クリで雲のパーティクル */ | |
case RECORD_9: { | |
if(act == Action.RIGHT_CLICK_AIR) { | |
Particle particle = new Particle(EnumParticle.CLOUD, eye, 0.1f, 0.5f, 0.1f, 0.5f, 10); | |
particle.sendParticle(player); | |
} | |
break; | |
} | |
/* 壊れたレコードで、空中右クリで(オフハンドの)アイテム破壊のパーティクル、ブロック右クリでブロック破壊のパーティクル */ | |
case RECORD_11: { | |
if(act == Action.RIGHT_CLICK_AIR && sub != null) { | |
EnumParticle type = EnumParticle.ITEM_CRACK; | |
type.setItemData(sub); | |
Particle particle = new Particle(type, eye, 0.1f, 0.1f, 0.1f, 0.2f, 20); | |
particle.sendParticle(player); | |
} | |
else if(act == Action.RIGHT_CLICK_BLOCK) { | |
Block block = event.getClickedBlock(); | |
Location loc = block.getLocation().add(0.5, 1.5, 0.5); | |
EnumParticle type = EnumParticle.BLOCK_CRACK.setBlockData(block); | |
Particle particle = new Particle(type, loc, 0.1f, 0.1f, 0.1f, 0.5f, 10); | |
particle.sendParticle(player); | |
} | |
break; | |
} | |
/* 緑と黄緑レコードで、空中右クリで水色に指定したレッドストーンのパーティクル */ | |
case RECORD_10: { | |
if(act == Action.RIGHT_CLICK_AIR) { | |
new Particle(EnumParticle.REDSTONE.setColor(Color.AQUA), eye, 0f,0f,0f,0f).sendParticle(player); | |
} | |
} | |
default: break; | |
} | |
} |
パーティクルの発生をサポートするメソッドです。 使い方はサンプルみて。
テストプレイとして1.7.2-R0.3~1.10-R0.1で動作確認しております。
基本的には EnumParticle.CLOUD と使えます。 ITEM_CRACK、BLOCK_CRACK、BLOCK_DUST の三種類に関しては setItemDataやsetBlockDataなどでパーティクルの詳細なデータ値を設定できます。 REDSTONEなど一部のパーティクルはsetColorより色指定が可能です。
また、EnumParticle.getParticle より パーティクル名あるいはID指定で取得も可能です。
フリー。勝手にどうぞ。