diff --git a/README.md b/README.md index 70c244f..936d854 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ For information about how to use the plugin or API within the plugin, please ref ### Server Compatibility This plugin is compatible with [Spigot](https://www.spigotmc.org/) and any forks, I recommend using [Paper](https://papermc.io/). Using CraftBukkit will not work. -The versions of Minecraft that are currently supported are 1.9.4-1.15.x. Support for 1.8.8 will not be added. +The versions of Minecraft that are currently supported are `1.15.2-1.7.10`. ### Compilation diff --git a/build.gradle b/build.gradle index 863e3a5..1e5b1e8 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 compileJava.options.encoding = 'UTF-8' group = 'dev.esophose' -version = '7.10' +version = '7.11' java { withJavadocJar() @@ -30,6 +30,8 @@ dependencies { compile 'org.slf4j:slf4j-api:1.7.25' compile 'org.slf4j:slf4j-nop:1.7.25' compile 'com.zaxxer:HikariCP:3.2.0' + compile 'org.bstats:bstats-bukkit-lite:1.7' + shadow 'com.googlecode.json-simple:json-simple:1.1.1' shadow 'org.jetbrains:annotations:16.0.2' shadow 'me.clip:placeholderapi:2.10.4' shadow 'org.xerial:sqlite-jdbc:3.23.1' @@ -39,6 +41,10 @@ dependencies { shadowJar { archiveClassifier.set(null) minimize() + + relocate('org.bstats', 'dev.esophose.playerparticles.libs.bstats') + relocate('org.slf4j', 'dev.esophose.playerparticles.libs.slf4j') + relocate('com.zaxxer.hikari', 'dev.esophose.playerparticles.libs.hikaricp') } processResources { diff --git a/changelog.txt b/changelog.txt index 530a6b0..e408248 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +=== v7.11 === ++ Added support for Spigot 1.8.8 and 1.7.10. I make no guarantees this will work perfectly. +* The plugin will now disable and print an error message on startup if the server is running CraftBukkit +* Removed deprecated API +* Other API-breaking changes (Moved the color data classes to a different package) +* Removed the version number from generating in new config.yml files === v7.10 === + Added command '/pp reset ' to be able to reset the particles of an offline player - Permission: 'playerparticles.reset.others' diff --git a/src/main/java/dev/esophose/playerparticles/PlayerParticles.java b/src/main/java/dev/esophose/playerparticles/PlayerParticles.java index f922535..3d37639 100644 --- a/src/main/java/dev/esophose/playerparticles/PlayerParticles.java +++ b/src/main/java/dev/esophose/playerparticles/PlayerParticles.java @@ -23,9 +23,11 @@ import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.manager.PluginUpdateManager; import dev.esophose.playerparticles.particles.listener.PPlayerCombatListener; import dev.esophose.playerparticles.particles.listener.PPlayerMovementListener; -import dev.esophose.playerparticles.util.Metrics; +import dev.esophose.playerparticles.util.LegacyMetrics; +import dev.esophose.playerparticles.util.NMSUtil; import java.util.LinkedHashMap; import java.util.Map; +import org.bstats.bukkit.MetricsLite; import org.bukkit.Bukkit; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; @@ -55,6 +57,12 @@ public class PlayerParticles extends JavaPlugin { */ @Override public void onEnable() { + if (!NMSUtil.isSpigot()) { + this.getLogger().severe("This plugin is only compatible with Spigot and other forks. CraftBukkit is not supported. Disabling PlayerParticles."); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + this.reload(); PluginManager pm = Bukkit.getPluginManager(); @@ -62,8 +70,13 @@ public class PlayerParticles extends JavaPlugin { pm.registerEvents(new PPlayerCombatListener(), this); pm.registerEvents(new PlayerChatHook(), this); - if (Setting.SEND_METRICS.getBoolean()) - new Metrics(this); + if (Setting.SEND_METRICS.getBoolean()) { + if (NMSUtil.getVersionNumber() > 7) { + new MetricsLite(this, 3531); + } else { + new LegacyMetrics(this); + } + } if (PlaceholderAPIHook.enabled()) new ParticlePlaceholderExpansion(this).register(); diff --git a/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java b/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java index f48146b..92c6153 100644 --- a/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java +++ b/src/main/java/dev/esophose/playerparticles/api/PlayerParticlesAPI.java @@ -10,10 +10,10 @@ import dev.esophose.playerparticles.particles.ConsolePPlayer; import dev.esophose.playerparticles.particles.FixedParticleEffect; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import java.util.ArrayList; import java.util.Collection; @@ -1059,34 +1059,4 @@ public final class PlayerParticlesAPI { //endregion - //region Registering Custom Styles - - /** - * Registers a particle style with the plugin - * - * @param particleStyle The particle style to register - * @deprecated Use {@link ParticleStyleRegistrationEvent} instead - */ - @Deprecated - public void registerParticleStyle(@NotNull ParticleStyle particleStyle) { - Objects.requireNonNull(particleStyle); - - this.playerParticles.getManager(ParticleStyleManager.class).registerStyle(particleStyle); - } - - /** - * Registers an event-based particle style with the plugin - * - * @param particleStyle The particle style to register - * @deprecated Use {@link ParticleStyleRegistrationEvent} instead - */ - @Deprecated - public void registerEventParticleStyle(@NotNull ParticleStyle particleStyle) { - Objects.requireNonNull(particleStyle); - - this.playerParticles.getManager(ParticleStyleManager.class).registerEventStyle(particleStyle); - } - - //endregion - } diff --git a/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java index 66eecb5..3292ce6 100644 --- a/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/AddCommandModule.java @@ -7,11 +7,11 @@ import dev.esophose.playerparticles.manager.ParticleStyleManager; import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; import dev.esophose.playerparticles.util.StringPlaceholders; diff --git a/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java index 953d98a..a0934ce 100644 --- a/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/EditCommandModule.java @@ -6,11 +6,11 @@ import dev.esophose.playerparticles.manager.LocaleManager; import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; import dev.esophose.playerparticles.util.StringPlaceholders; diff --git a/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java b/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java index 4a190a0..d436948 100644 --- a/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java +++ b/src/main/java/dev/esophose/playerparticles/command/FixedCommandModule.java @@ -7,10 +7,10 @@ import dev.esophose.playerparticles.manager.PermissionManager; import dev.esophose.playerparticles.particles.FixedParticleEffect; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; import dev.esophose.playerparticles.util.StringPlaceholders; diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiActionButton.java b/src/main/java/dev/esophose/playerparticles/gui/GuiActionButton.java index 7e90486..bca3e78 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiActionButton.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiActionButton.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.gui; import dev.esophose.playerparticles.gui.GuiInventoryEditData.ColorData; import dev.esophose.playerparticles.hook.PlaceholderAPIHook; import dev.esophose.playerparticles.particles.PPlayer; +import dev.esophose.playerparticles.util.NMSUtil; import java.util.ArrayList; import java.util.List; import org.bukkit.ChatColor; @@ -107,7 +108,9 @@ public class GuiActionButton { if (this.colors[0].getMaterial() != null) { // Use Materials itemStack = new ItemStack(this.colors[this.iconIndex].getMaterial()); } else { // Use Dyes - itemStack = new Dye(this.colors[this.iconIndex].getDyeColor()).toItemStack(1); + Dye dye = new Dye(); + dye.setColor(this.colors[this.iconIndex].getDyeColor()); + itemStack = dye.toItemStack(1); } } @@ -115,7 +118,8 @@ public class GuiActionButton { if (itemMeta != null) { itemMeta.setDisplayName(PlaceholderAPIHook.applyPlaceholders(pplayer.getPlayer(), this.name)); itemMeta.setLore(parseLore(pplayer, this.lore)); - itemMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_POTION_EFFECTS); + if (NMSUtil.getVersionNumber() > 7) + itemMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_POTION_EFFECTS); itemStack.setItemMeta(itemMeta); } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java index e26dd5c..1c97d0d 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventory.java @@ -3,6 +3,7 @@ package dev.esophose.playerparticles.gui; import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.ConfigurationManager.Setting; import dev.esophose.playerparticles.particles.PPlayer; +import dev.esophose.playerparticles.util.NMSUtil; import dev.esophose.playerparticles.util.ParticleUtils; import java.util.ArrayList; import java.util.List; @@ -56,7 +57,8 @@ public abstract class GuiInventory { ItemMeta meta = borderIcon.getItemMeta(); if (meta != null) { meta.setDisplayName(" "); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_POTION_EFFECTS, ItemFlag.HIDE_ENCHANTS); + if (NMSUtil.getVersionNumber() > 7) + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_POTION_EFFECTS, ItemFlag.HIDE_ENCHANTS); borderIcon.setItemMeta(meta); } @@ -155,7 +157,11 @@ public abstract class GuiInventory { button.handleClick(isShiftClick); if (Setting.GUI_BUTTON_SOUND.getBoolean() && event.getWhoClicked() instanceof Player) { Player player = (Player) event.getWhoClicked(); - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 1); + if (NMSUtil.getVersionNumber() > 8) { + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 1); + } else { + player.playSound(player.getLocation(), Sound.valueOf("CLICK"), 0.5f, 1); + } } break; } diff --git a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java index d0ccd15..090627c 100644 --- a/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java +++ b/src/main/java/dev/esophose/playerparticles/gui/GuiInventoryEditData.java @@ -6,10 +6,10 @@ import dev.esophose.playerparticles.manager.GuiManager; import dev.esophose.playerparticles.manager.LocaleManager; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.util.NMSUtil; import dev.esophose.playerparticles.util.ParticleUtils; import dev.esophose.playerparticles.util.StringPlaceholders; diff --git a/src/main/java/dev/esophose/playerparticles/manager/ConfigurationManager.java b/src/main/java/dev/esophose/playerparticles/manager/ConfigurationManager.java index b10da8c..5d1ebac 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/ConfigurationManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/ConfigurationManager.java @@ -16,11 +16,11 @@ import org.bukkit.Material; public class ConfigurationManager extends Manager { private static final String[] HEADER = new String[] { - " _________ __ __________ __ __ __ _________", - " \\______ \\ | _____ ___ __ __________\\______ \\_____ ________/ |_|__| ____ | | ____ ______ \\______ \\", - " | ___/ | \\__ \\< | |/ __ \\_ __ \\ ___/\\__ \\\\_ __ \\ __\\ |/ ___\\| | _/ __ \\ / ___/ / /", - " | | | |__/ __ \\\\___ \\ ___/| | \\/ | / __ \\| | \\/| | | \\ \\___| |_\\ ___/ \\___ \\ / /", - " |____| |____(____ / ____|\\___ >__| |____| (____ /__| |__| |__|\\___ >____/\\___ >____ > /____/", + " _________ __ __________ __ __ __", + " \\______ \\ | _____ ___ __ __________\\______ \\_____ ________/ |_|__| ____ | | ____ ______", + " | ___/ | \\__ \\< | |/ __ \\_ __ \\ ___/\\__ \\\\_ __ \\ __\\ |/ ___\\| | _/ __ \\ / ___/", + " | | | |__/ __ \\\\___ \\ ___/| | \\/ | / __ \\| | \\/| | | \\ \\___| |_\\ ___/ \\___ \\", + " |____| |____(____ / ____|\\___ >__| |____| (____ /__| |__| |__|\\___ >____/\\___ >____ >", " \\/\\/ \\/ \\/ \\/ \\/ \\/" }; @@ -332,7 +332,7 @@ public class ConfigurationManager extends Manager { } if (material == null) - material = Material.BARRIER; + material = ParticleUtils.FALLBACK_MATERIAL; this.materials.put(configPath, material); diff --git a/src/main/java/dev/esophose/playerparticles/manager/DataManager.java b/src/main/java/dev/esophose/playerparticles/manager/DataManager.java index d511676..532127d 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/DataManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/DataManager.java @@ -9,10 +9,10 @@ import dev.esophose.playerparticles.particles.ConsolePPlayer; import dev.esophose.playerparticles.particles.FixedParticleEffect; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; import java.sql.PreparedStatement; diff --git a/src/main/java/dev/esophose/playerparticles/manager/ParticleGroupPresetManager.java b/src/main/java/dev/esophose/playerparticles/manager/ParticleGroupPresetManager.java index 147cfcc..9b8a000 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/ParticleGroupPresetManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/ParticleGroupPresetManager.java @@ -3,12 +3,12 @@ package dev.esophose.playerparticles.manager; import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; import dev.esophose.playerparticles.particles.ParticleGroup; import dev.esophose.playerparticles.particles.ParticleGroupPreset; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.inputparser.InputParser; import java.io.File; diff --git a/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java b/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java index 18ead0b..ab48cc4 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/ParticleManager.java @@ -7,10 +7,11 @@ import dev.esophose.playerparticles.particles.FixedParticleEffect; import dev.esophose.playerparticles.particles.PParticle; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.DefaultStyles; +import dev.esophose.playerparticles.util.NMSUtil; import java.awt.Color; import java.util.Collection; import java.util.List; @@ -151,7 +152,7 @@ public class ParticleManager extends Manager implements Listener, Runnable { // Don't show their particles if they are in spectator mode // Don't spawn particles if the world doesn't allow it - if (player != null && player.getGameMode() != GameMode.SPECTATOR && permissionManager.isWorldEnabled(player.getWorld().getName())) + if (player != null && (NMSUtil.getVersionNumber() < 8 || player.getGameMode() != GameMode.SPECTATOR) && permissionManager.isWorldEnabled(player.getWorld().getName())) for (ParticlePair particles : pplayer.getActiveParticles()) this.displayParticles(pplayer, particles, player.getLocation().clone().add(0, 1, 0)); @@ -214,7 +215,7 @@ public class ParticleManager extends Manager implements Listener, Runnable { */ public void displayParticles(Player player, World world, ParticlePair particle, List particles, boolean isLongRange) { PermissionManager permissionManager = this.playerParticles.getManager(PermissionManager.class); - if ((player != null && player.getGameMode() == GameMode.SPECTATOR) || !permissionManager.isWorldEnabled(world.getName())) + if ((player != null && (NMSUtil.getVersionNumber() < 8 || player.getGameMode() == GameMode.SPECTATOR)) || !permissionManager.isWorldEnabled(world.getName())) return; for (PParticle pparticle : particles) diff --git a/src/main/java/dev/esophose/playerparticles/manager/ParticleStyleManager.java b/src/main/java/dev/esophose/playerparticles/manager/ParticleStyleManager.java index cc8d117..2c53868 100644 --- a/src/main/java/dev/esophose/playerparticles/manager/ParticleStyleManager.java +++ b/src/main/java/dev/esophose/playerparticles/manager/ParticleStyleManager.java @@ -74,43 +74,6 @@ public class ParticleStyleManager extends Manager { } - /** - * Registers a style that is put into the plugin's update loop - * - * @param style The style to add - */ - @Deprecated - public void registerStyle(ParticleStyle style) { - if (style == null) { - throw new IllegalArgumentException("Tried to register a null style"); - } - - if (style.getInternalName() == null || style.getInternalName().trim().equals("")) { - throw new IllegalArgumentException("Tried to register a style with a null or empty name: '" + style.getInternalName() + "'"); - } - - for (ParticleStyle testAgainst : this.styles) { - if (testAgainst.equals(style)) { - throw new IllegalArgumentException("Tried to register the same style twice: '" + style.getInternalName() + "'"); - } else if (testAgainst.getInternalName().equalsIgnoreCase(style.getInternalName())) { - throw new IllegalArgumentException("Tried to register two styles with the same internal name spelling: '" + style.getInternalName() + "'"); - } - } - - this.styles.add(style); - } - - /** - * Registers a style that isn't updated on the normal update loop - * - * @param style The style to register - */ - @Deprecated - public void registerEventStyle(ParticleStyle style) { - this.registerStyle(style); - this.eventStyles.add(style); - } - /** * Returns if a given style is customly handled * diff --git a/src/main/java/dev/esophose/playerparticles/particles/ParticleEffect.java b/src/main/java/dev/esophose/playerparticles/particles/ParticleEffect.java index 762b387..67e1e7e 100644 --- a/src/main/java/dev/esophose/playerparticles/particles/ParticleEffect.java +++ b/src/main/java/dev/esophose/playerparticles/particles/ParticleEffect.java @@ -3,8 +3,13 @@ package dev.esophose.playerparticles.particles; import com.google.common.collect.ObjectArrays; import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.config.CommentedFileConfiguration; -import dev.esophose.playerparticles.manager.ConfigurationManager.Setting; -import dev.esophose.playerparticles.manager.ParticleManager; +import dev.esophose.playerparticles.particles.data.ParticleColor; +import dev.esophose.playerparticles.particles.spawning.ParticleSpawner; +import dev.esophose.playerparticles.particles.spawning.ParticleSpawner.ParticleColorException; +import dev.esophose.playerparticles.particles.spawning.ParticleSpawner.ParticleDataException; +import dev.esophose.playerparticles.particles.spawning.ReflectiveParticleSpawner; +import dev.esophose.playerparticles.particles.spawning.SpigotParticleSpawner; +import dev.esophose.playerparticles.particles.spawning.reflective.ReflectiveParticleEffectMapping; import dev.esophose.playerparticles.util.NMSUtil; import dev.esophose.playerparticles.util.ParticleUtils; import java.io.File; @@ -12,19 +17,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.stream.Stream; -import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Particle; -import org.bukkit.Particle.DustOptions; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; -import org.bukkit.metadata.MetadataValue; -@SuppressWarnings("deprecation") public enum ParticleEffect { // Ordered and named by their Minecraft 1.13+ internal names @@ -92,8 +90,14 @@ public enum ParticleEffect { UNDERWATER("SUSPENDED_DEPTH", Arrays.asList("TURTLE_HELMET", "SPONGE")), WITCH("SPELL_WITCH", Collections.singletonList("CAULDRON")); + private final static ParticleSpawner particleSpawner; + static { + particleSpawner = NMSUtil.getVersionNumber() >= 9 ? new SpigotParticleSpawner() : new ReflectiveParticleSpawner(); + } + private Particle internalEnum; private List properties; + private boolean supported; private CommentedFileConfiguration config; private boolean enabledByDefault; @@ -128,7 +132,16 @@ public enum ParticleEffect { this.properties = Arrays.asList(properties); // Will be null if this server's version doesn't support this particle type - this.internalEnum = Stream.of(Particle.values()).filter(x -> x.name().equals(enumName)).findFirst().orElse(null); + if (NMSUtil.getVersionNumber() > 8) { + this.internalEnum = Stream.of(Particle.values()).filter(x -> x.name().equals(enumName)).findFirst().orElse(null); + this.supported = this.internalEnum != null; + } else { + try { + this.supported = ReflectiveParticleEffectMapping.valueOf(this.name()).isSupported(); + } catch (Exception e) { + this.supported = false; + } + } this.setDefaultSettings(); this.loadSettings(false); @@ -217,6 +230,13 @@ public enum ParticleEffect { return this.effectName; } + /** + * @return the Spigot enum this represents + */ + public Particle getSpigotEnum() { + return this.internalEnum; + } + /** * @return The Material icon that represents this style in the GUI */ @@ -240,7 +260,7 @@ public enum ParticleEffect { * @return Whether the particle effect is supported or not */ public boolean isSupported() { - return this.internalEnum != null; + return this.supported; } /** @@ -282,10 +302,10 @@ public enum ParticleEffect { public static ParticleEffect fromInternalName(String internalName) { return Stream.of(values()).filter(x -> x.isSupported() && x.isEnabled() && x.getInternalName().equalsIgnoreCase(internalName)).findFirst().orElse(null); } - + /** * Invokes the correct spawn method for the particle information given - * + * * @param particle The ParticlePair, given the effect/style/data * @param pparticle The particle spawn information * @param isLongRange If the particle can be viewed from long range @@ -294,7 +314,7 @@ public enum ParticleEffect { public static void display(ParticlePair particle, PParticle pparticle, boolean isLongRange, Player owner) { ParticleEffect effect = particle.getEffect(); int count = pparticle.isDirectional() ? 0 : 1; - + if (effect.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) { effect.display(particle.getSpawnMaterial(), pparticle.getXOff(), pparticle.getYOff(), pparticle.getZOff(), pparticle.getSpeed(), 1, pparticle.getLocation(false), isLongRange, owner); } else if (effect.hasProperty(ParticleProperty.COLORABLE)) { @@ -306,7 +326,7 @@ public enum ParticleEffect { /** * Displays a particle 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 @@ -317,12 +337,8 @@ public enum ParticleEffect { * @param owner The player that owns the particles * @throws ParticleDataException If the particle effect requires additional data */ - public void display(double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) throws ParticleDataException { - if (this.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) - throw new ParticleDataException("This particle effect requires additional data"); - - for (Player player : this.getPlayersInRange(center, isLongRange, owner)) - player.spawnParticle(this.internalEnum, center.getX(), center.getY(), center.getZ(), amount, offsetX, offsetY, offsetZ, speed); + public void display(double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + particleSpawner.display(this, offsetX, offsetY, offsetZ, speed, amount, center, isLongRange, owner); } /** @@ -334,21 +350,8 @@ public enum ParticleEffect { * @param owner The player that owns the particles * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect */ - public void display(ParticleColor color, Location center, boolean isLongRange, Player owner) throws ParticleColorException { - if (!this.hasProperty(ParticleProperty.COLORABLE)) - throw new ParticleColorException("This particle effect is not colorable"); - - if (this == DUST && NMSUtil.getVersionNumber() >= 13) { // DUST uses a special data object for spawning in 1.13+ - OrdinaryColor dustColor = (OrdinaryColor) color; - DustOptions dustOptions = new DustOptions(Color.fromRGB(dustColor.getRed(), dustColor.getGreen(), dustColor.getBlue()), Setting.DUST_SIZE.getFloat()); - for (Player player : this.getPlayersInRange(center, isLongRange, owner)) - player.spawnParticle(this.internalEnum, center.getX(), center.getY(), center.getZ(), 1, 0, 0, 0, 0, dustOptions); - } else { - for (Player player : this.getPlayersInRange(center, isLongRange, owner)) { - // Minecraft clients require that you pass a non-zero value if the Red value should be zero - player.spawnParticle(this.internalEnum, center.getX(), center.getY(), center.getZ(), 0, this == ParticleEffect.DUST && color.getValueX() == 0 ? Float.MIN_VALUE : color.getValueX(), color.getValueY(), color.getValueZ(), 1); - } - } + public void display(ParticleColor color, Location center, boolean isLongRange, Player owner) { + particleSpawner.display(this, color, center, isLongRange, owner); } /** @@ -367,65 +370,8 @@ public enum ParticleEffect { * @param owner The player that owns the particles * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect */ - public void display(Material spawnMaterial, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) throws ParticleDataException { - if (!this.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) { - throw new ParticleDataException("This particle effect does not require additional data"); - } - - Object extraData = null; - if (this.internalEnum.getDataType().getTypeName().equals("org.bukkit.block.data.BlockData")) { - extraData = spawnMaterial.createBlockData(); - } else if (this.internalEnum.getDataType() == ItemStack.class) { - extraData = new ItemStack(spawnMaterial); - } else if (this.internalEnum.getDataType() == MaterialData.class) { - extraData = new MaterialData(spawnMaterial); // Deprecated, only used in versions < 1.13 - } - - for (Player player : this.getPlayersInRange(center, isLongRange, owner)) - player.spawnParticle(this.internalEnum, center.getX(), center.getY(), center.getZ(), amount, offsetX, offsetY, offsetZ, speed, extraData); - } - - /** - * Gets a List of Players within the particle display range - * - * @param center The center of the radius to check around - * @param isLongRange If the particle can be viewed from long range - * @param owner The player that owns the particles - * @return A List of Players within the particle display range - */ - private List getPlayersInRange(Location center, boolean isLongRange, Player owner) { - List players = new ArrayList<>(); - int range = !isLongRange ? Setting.PARTICLE_RENDER_RANGE_PLAYER.getInt() : Setting.PARTICLE_RENDER_RANGE_FIXED_EFFECT.getInt(); - range *= range; - - for (PPlayer pplayer : PlayerParticles.getInstance().getManager(ParticleManager.class).getPPlayers()) { - Player p = pplayer.getPlayer(); - if (!this.canSee(p, owner)) - continue; - - if (p != null && pplayer.canSeeParticles() && p.getWorld().equals(center.getWorld()) && center.distanceSquared(p.getLocation()) <= range) - players.add(p); - } - - return players; - } - - /** - * Checks if a player can see another player - * - * @param player The player - * @param target The target - * @return True if player can see target, otherwise false - */ - private boolean canSee(Player player, Player target) { - if (player == null || target == null) - return true; - - for (MetadataValue meta : target.getMetadata("vanished")) - if (meta.asBoolean()) - return false; - - return player.canSee(target); + public void display(Material spawnMaterial, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + particleSpawner.display(this, spawnMaterial, offsetX, offsetY, offsetZ, speed, amount, center, isLongRange, owner); } /** @@ -448,310 +394,4 @@ public enum ParticleEffect { COLORABLE } - /** - * Represents the color for effects like {@link ParticleEffect#ENTITY_EFFECT}, - * {@link ParticleEffect#AMBIENT_ENTITY_EFFECT}, {@link ParticleEffect#DUST} - * and {@link ParticleEffect#NOTE} - *

- * This class is part of the ParticleEffect Library and follows the - * same usage conditions - * - * @author DarkBlade12 - * @since 1.7 - */ - public static abstract class ParticleColor { - /** - * Returns the value for the offsetX field - * - * @return The offsetX value - */ - public abstract float getValueX(); - - /** - * Returns the value for the offsetY field - * - * @return The offsetY value - */ - public abstract float getValueY(); - - /** - * Returns the value for the offsetZ field - * - * @return The offsetZ value - */ - public abstract float getValueZ(); - } - - /** - * Represents the color for effects like {@link ParticleEffect#ENTITY_EFFECT}, - * {@link ParticleEffect#AMBIENT_ENTITY_EFFECT} and {@link ParticleEffect#NOTE} - *

- * This class is part of the ParticleEffect Library and follows the - * same usage conditions - * - * @author DarkBlade12 - * @since 1.7 - */ - public static final class OrdinaryColor extends ParticleColor { - public static final OrdinaryColor RAINBOW = new OrdinaryColor(999, 999, 999); - public static final OrdinaryColor RANDOM = new OrdinaryColor(998, 998, 998); - - private final int red; - private final int green; - private final int blue; - - /** - * Construct a new ordinary color - * - * @param red Red value of the RGB format - * @param green Green value of the RGB format - * @param blue Blue value of the RGB format - * @throws IllegalArgumentException If one of the values is lower than 0 - * or higher than 255 - */ - public OrdinaryColor(int red, int green, int blue) throws IllegalArgumentException { - if ((red == 999 && green == 999 && blue == 999) || (red == 998 && green == 998 && blue == 998)) { // Allow rainbow and random values - this.red = red; - this.green = green; - this.blue = blue; - } else { - if (red < 0) { - throw new IllegalArgumentException("The red value is lower than 0"); - } - if (red > 255) { - throw new IllegalArgumentException("The red value is higher than 255"); - } - this.red = red; - if (green < 0) { - throw new IllegalArgumentException("The green value is lower than 0"); - } - if (green > 255) { - throw new IllegalArgumentException("The green value is higher than 255"); - } - this.green = green; - if (blue < 0) { - throw new IllegalArgumentException("The blue value is lower than 0"); - } - if (blue > 255) { - throw new IllegalArgumentException("The blue value is higher than 255"); - } - this.blue = blue; - } - } - - /** - * Returns the red value of the RGB format - * - * @return The red value - */ - public int getRed() { - return this.red; - } - - /** - * Returns the green value of the RGB format - * - * @return The green value - */ - public int getGreen() { - return this.green; - } - - /** - * Returns the blue value of the RGB format - * - * @return The blue value - */ - public int getBlue() { - return this.blue; - } - - /** - * Returns the red value divided by 255 - * - * @return The offsetX value - */ - @Override - public float getValueX() { - if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) - return 0F; - return (float) this.red / 255F; - } - - /** - * Returns the green value divided by 255 - * - * @return The offsetY value - */ - @Override - public float getValueY() { - if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) - return 0F; - return (float) this.green / 255F; - } - - /** - * Returns the blue value divided by 255 - * - * @return The offsetZ value - */ - @Override - public float getValueZ() { - if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) - return 0F; - return (float) this.blue / 255F; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof OrdinaryColor)) - return false; - OrdinaryColor otherColor = (OrdinaryColor) other; - return this.red == otherColor.red && this.green == otherColor.green && this.blue == otherColor.blue; - } - - @Override - public int hashCode() { - return Objects.hash(this.red, this.green, this.blue); - } - } - - /** - * Represents the color for the {@link ParticleEffect#NOTE} effect - *

- * This class is part of the ParticleEffect Library and follows the - * same usage conditions - * - * @author DarkBlade12 - * @since 1.7 - */ - public static final class NoteColor extends ParticleColor { - public static final NoteColor RAINBOW = new NoteColor(99); - public static final NoteColor RANDOM = new NoteColor(98); - - private final int note; - - /** - * Construct a new note color - * - * @param note Note id which determines color - * @throws IllegalArgumentException If the note value is lower than 0 or - * higher than 24 - */ - public NoteColor(int note) throws IllegalArgumentException { - if (note == 99 || note == 98) { // Allow rainbow and random values - this.note = note; - } else { - if (note < 0) { - throw new IllegalArgumentException("The note value is lower than 0"); - } - if (note > 24) { - throw new IllegalArgumentException("The note value is higher than 24"); - } - this.note = note; - } - } - - /** - * Returns the note value - * - * @return The note value - */ - public int getNote() { - return this.note; - } - - /** - * Returns the note value divided by 24 - * - * @return The offsetX value - */ - @Override - public float getValueX() { - return (float) this.note / 24F; - } - - /** - * Returns zero because the offsetY value is unused - * - * @return zero - */ - @Override - public float getValueY() { - return 0; - } - - /** - * Returns zero because the offsetZ value is unused - * - * @return zero - */ - @Override - public float getValueZ() { - return 0; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof NoteColor)) - return false; - NoteColor otherColor = (NoteColor) other; - return this.note == otherColor.note; - } - - @Override - public int hashCode() { - return Objects.hashCode(this.note); - } - - } - - /** - * Represents a runtime exception that is thrown either if the displayed - * particle effect requires data and has none or vice-versa or if the data - * type is incorrect - *

- * This class is part of the ParticleEffect Library and follows the - * same usage conditions - * - * @author DarkBlade12 - * @since 1.6 - */ - private static final class ParticleDataException extends RuntimeException { - private static final long serialVersionUID = 3203085387160737484L; - - /** - * Construct a new particle data exception - * - * @param message Message that will be logged - */ - public ParticleDataException(String message) { - super(message); - } - } - - /** - * Represents a runtime exception that is thrown either if the displayed - * particle effect is not colorable or if the particle color type is - * incorrect - *

- * This class is part of the ParticleEffect Library and follows the - * same usage conditions - * - * @author DarkBlade12 - * @since 1.7 - */ - private static final class ParticleColorException extends RuntimeException { - private static final long serialVersionUID = 3203085387160737485L; - - /** - * Construct a new particle color exception - * - * @param message Message that will be logged - */ - public ParticleColorException(String message) { - super(message); - } - } - } diff --git a/src/main/java/dev/esophose/playerparticles/particles/ParticlePair.java b/src/main/java/dev/esophose/playerparticles/particles/ParticlePair.java index db0ff2b..bffcf56 100644 --- a/src/main/java/dev/esophose/playerparticles/particles/ParticlePair.java +++ b/src/main/java/dev/esophose/playerparticles/particles/ParticlePair.java @@ -3,10 +3,10 @@ package dev.esophose.playerparticles.particles; import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.LocaleManager; import dev.esophose.playerparticles.manager.ParticleManager; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; -import dev.esophose.playerparticles.particles.ParticleEffect.ParticleColor; import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; +import dev.esophose.playerparticles.particles.data.ParticleColor; import dev.esophose.playerparticles.styles.DefaultStyles; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.ParticleUtils; diff --git a/src/main/java/dev/esophose/playerparticles/particles/data/NoteColor.java b/src/main/java/dev/esophose/playerparticles/particles/data/NoteColor.java new file mode 100644 index 0000000..b9b0da1 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/data/NoteColor.java @@ -0,0 +1,94 @@ +package dev.esophose.playerparticles.particles.data; + +import dev.esophose.playerparticles.particles.ParticleEffect; +import java.util.Objects; + +/** + * Represents the color for the {@link ParticleEffect#NOTE} effect + *

+ * This class is part of the ParticleEffect Library and follows the + * same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ +public final class NoteColor extends ParticleColor { + public static final NoteColor RAINBOW = new NoteColor(99); + public static final NoteColor RANDOM = new NoteColor(98); + + private final int note; + + /** + * Construct a new note color + * + * @param note Note id which determines color + * @throws IllegalArgumentException If the note value is lower than 0 or + * higher than 24 + */ + public NoteColor(int note) throws IllegalArgumentException { + if (note == 99 || note == 98) { // Allow rainbow and random values + this.note = note; + } else { + if (note < 0) { + throw new IllegalArgumentException("The note value is lower than 0"); + } + if (note > 24) { + throw new IllegalArgumentException("The note value is higher than 24"); + } + this.note = note; + } + } + + /** + * Returns the note value + * + * @return The note value + */ + public int getNote() { + return this.note; + } + + /** + * Returns the note value divided by 24 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + return (float) this.note / 24F; + } + + /** + * Returns zero because the offsetY value is unused + * + * @return zero + */ + @Override + public float getValueY() { + return 0; + } + + /** + * Returns zero because the offsetZ value is unused + * + * @return zero + */ + @Override + public float getValueZ() { + return 0; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof NoteColor)) + return false; + NoteColor otherColor = (NoteColor) other; + return this.note == otherColor.note; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.note); + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/data/OrdinaryColor.java b/src/main/java/dev/esophose/playerparticles/particles/data/OrdinaryColor.java new file mode 100644 index 0000000..a43b724 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/data/OrdinaryColor.java @@ -0,0 +1,138 @@ +package dev.esophose.playerparticles.particles.data; + +import dev.esophose.playerparticles.particles.ParticleEffect; +import java.util.Objects; + +/** + * Represents the color for effects like {@link ParticleEffect#ENTITY_EFFECT}, + * {@link ParticleEffect#AMBIENT_ENTITY_EFFECT} and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the + * same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ +public final class OrdinaryColor extends ParticleColor { + public static final OrdinaryColor RAINBOW = new OrdinaryColor(999, 999, 999); + public static final OrdinaryColor RANDOM = new OrdinaryColor(998, 998, 998); + + private final int red; + private final int green; + private final int blue; + + /** + * Construct a new ordinary color + * + * @param red Red value of the RGB format + * @param green Green value of the RGB format + * @param blue Blue value of the RGB format + * @throws IllegalArgumentException If one of the values is lower than 0 + * or higher than 255 + */ + public OrdinaryColor(int red, int green, int blue) throws IllegalArgumentException { + if ((red == 999 && green == 999 && blue == 999) || (red == 998 && green == 998 && blue == 998)) { // Allow rainbow and random values + this.red = red; + this.green = green; + this.blue = blue; + } else { + if (red < 0) { + throw new IllegalArgumentException("The red value is lower than 0"); + } + if (red > 255) { + throw new IllegalArgumentException("The red value is higher than 255"); + } + this.red = red; + if (green < 0) { + throw new IllegalArgumentException("The green value is lower than 0"); + } + if (green > 255) { + throw new IllegalArgumentException("The green value is higher than 255"); + } + this.green = green; + if (blue < 0) { + throw new IllegalArgumentException("The blue value is lower than 0"); + } + if (blue > 255) { + throw new IllegalArgumentException("The blue value is higher than 255"); + } + this.blue = blue; + } + } + + /** + * Returns the red value of the RGB format + * + * @return The red value + */ + public int getRed() { + return this.red; + } + + /** + * Returns the green value of the RGB format + * + * @return The green value + */ + public int getGreen() { + return this.green; + } + + /** + * Returns the blue value of the RGB format + * + * @return The blue value + */ + public int getBlue() { + return this.blue; + } + + /** + * Returns the red value divided by 255 + * + * @return The offsetX value + */ + @Override + public float getValueX() { + if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) + return 0F; + return (float) this.red / 255F; + } + + /** + * Returns the green value divided by 255 + * + * @return The offsetY value + */ + @Override + public float getValueY() { + if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) + return 0F; + return (float) this.green / 255F; + } + + /** + * Returns the blue value divided by 255 + * + * @return The offsetZ value + */ + @Override + public float getValueZ() { + if (this.equals(OrdinaryColor.RAINBOW) || this.equals(OrdinaryColor.RANDOM)) + return 0F; + return (float) this.blue / 255F; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof OrdinaryColor)) + return false; + OrdinaryColor otherColor = (OrdinaryColor) other; + return this.red == otherColor.red && this.green == otherColor.green && this.blue == otherColor.blue; + } + + @Override + public int hashCode() { + return Objects.hash(this.red, this.green, this.blue); + } +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/data/ParticleColor.java b/src/main/java/dev/esophose/playerparticles/particles/data/ParticleColor.java new file mode 100644 index 0000000..4c8d4c8 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/data/ParticleColor.java @@ -0,0 +1,37 @@ +package dev.esophose.playerparticles.particles.data; + +import dev.esophose.playerparticles.particles.ParticleEffect; + +/** + * Represents the color for effects like {@link ParticleEffect#ENTITY_EFFECT}, + * {@link ParticleEffect#AMBIENT_ENTITY_EFFECT}, {@link ParticleEffect#DUST} + * and {@link ParticleEffect#NOTE} + *

+ * This class is part of the ParticleEffect Library and follows the + * same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ +public abstract class ParticleColor { + /** + * Returns the value for the offsetX field + * + * @return The offsetX value + */ + public abstract float getValueX(); + + /** + * Returns the value for the offsetY field + * + * @return The offsetY value + */ + public abstract float getValueY(); + + /** + * Returns the value for the offsetZ field + * + * @return The offsetZ value + */ + public abstract float getValueZ(); +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/spawning/ParticleSpawner.java b/src/main/java/dev/esophose/playerparticles/particles/spawning/ParticleSpawner.java new file mode 100644 index 0000000..993b372 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/spawning/ParticleSpawner.java @@ -0,0 +1,156 @@ +package dev.esophose.playerparticles.particles.spawning; + +import dev.esophose.playerparticles.PlayerParticles; +import dev.esophose.playerparticles.manager.ConfigurationManager.Setting; +import dev.esophose.playerparticles.manager.ParticleManager; +import dev.esophose.playerparticles.particles.PPlayer; +import dev.esophose.playerparticles.particles.ParticleEffect; +import dev.esophose.playerparticles.particles.data.ParticleColor; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.metadata.MetadataValue; + +public abstract class ParticleSpawner { + + /** + * Displays a particle effect + * + * @param particleEffect The particle type to display + * @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 isLongRange If the particle can be viewed from long range + * @param owner The player that owns the particles + * @throws ParticleDataException If the particle effect requires additional data + */ + public abstract void display(ParticleEffect particleEffect, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner); + + /** + * Displays a single particle which is colored + * + * @param particleEffect The particle type to display + * @param color Color of the particle + * @param center Center location of the effect + * @param isLongRange If the particle can be viewed from long range + * @param owner The player that owns the particles + * @throws ParticleColorException If the particle effect is not colorable or the color type is incorrect + */ + public abstract void display(ParticleEffect particleEffect, ParticleColor color, Location center, boolean isLongRange, Player owner); + + /** + * Displays a particle effect which requires additional data and is only + * visible for all players within a certain range in the world of @param + * center + * + * @param particleEffect The particle type to display + * @param spawnMaterial Material 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 + * @param center Center location of the effect + * @param isLongRange If the particle can be viewed from long range + * @param owner The player that owns the particles + * @throws ParticleDataException If the particle effect does not require additional data or if the data type is incorrect + */ + public abstract void display(ParticleEffect particleEffect, Material spawnMaterial, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner); + + /** + * Gets a List of Players within the particle display range + * + * @param center The center of the radius to check around + * @param isLongRange If the particle can be viewed from long range + * @param owner The player that owns the particles + * @return A List of Players within the particle display range + */ + protected List getPlayersInRange(Location center, boolean isLongRange, Player owner) { + List players = new ArrayList<>(); + int range = !isLongRange ? Setting.PARTICLE_RENDER_RANGE_PLAYER.getInt() : Setting.PARTICLE_RENDER_RANGE_FIXED_EFFECT.getInt(); + range *= range; + + for (PPlayer pplayer : PlayerParticles.getInstance().getManager(ParticleManager.class).getPPlayers()) { + Player p = pplayer.getPlayer(); + if (p != owner && !this.canSee(p, owner)) + continue; + + if (p != null && pplayer.canSeeParticles() && p.getWorld().equals(center.getWorld()) && center.distanceSquared(p.getLocation()) <= range) + players.add(p); + } + + return players; + } + + /** + * Checks if a player can see another player + * + * @param player The player + * @param target The target + * @return True if player can see target, otherwise false + */ + private boolean canSee(Player player, Player target) { + if (player == null || target == null) + return true; + + for (MetadataValue meta : target.getMetadata("vanished")) + if (meta.asBoolean()) + return false; + + return player.canSee(target); + } + + /** + * Represents a runtime exception that is thrown either if the displayed + * particle effect requires data and has none or vice-versa or if the data + * type is incorrect + *

+ * This class is part of the ParticleEffect Library and follows the + * same usage conditions + * + * @author DarkBlade12 + * @since 1.6 + */ + public static final class ParticleDataException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737484L; + + /** + * Construct a new particle data exception + * + * @param message Message that will be logged + */ + public ParticleDataException(String message) { + super(message); + } + } + + /** + * Represents a runtime exception that is thrown either if the displayed + * particle effect is not colorable or if the particle color type is + * incorrect + *

+ * This class is part of the ParticleEffect Library and follows the + * same usage conditions + * + * @author DarkBlade12 + * @since 1.7 + */ + public static final class ParticleColorException extends RuntimeException { + private static final long serialVersionUID = 3203085387160737485L; + + /** + * Construct a new particle color exception + * + * @param message Message that will be logged + */ + public ParticleColorException(String message) { + super(message); + } + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/spawning/ReflectiveParticleSpawner.java b/src/main/java/dev/esophose/playerparticles/particles/spawning/ReflectiveParticleSpawner.java new file mode 100644 index 0000000..f6ea5ec --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/spawning/ReflectiveParticleSpawner.java @@ -0,0 +1,284 @@ +package dev.esophose.playerparticles.particles.spawning; + +import dev.esophose.playerparticles.particles.ParticleEffect; +import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; +import dev.esophose.playerparticles.particles.data.ParticleColor; +import dev.esophose.playerparticles.particles.spawning.reflective.ReflectionUtils; +import dev.esophose.playerparticles.particles.spawning.reflective.ReflectionUtils.PackageType; +import dev.esophose.playerparticles.particles.spawning.reflective.ReflectiveParticleEffectMapping; +import dev.esophose.playerparticles.util.NMSUtil; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +public class ReflectiveParticleSpawner extends ParticleSpawner { + + @Override + public void display(ParticleEffect particleEffect, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + if (particleEffect.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) + throw new ParticleDataException("This particle effect requires additional data"); + + new ParticlePacket(particleEffect, offsetX, offsetY, offsetZ, speed, amount, true, null).sendTo(center, this.getPlayersInRange(center, isLongRange, owner)); + } + + @Override + public void display(ParticleEffect particleEffect, ParticleColor color, Location center, boolean isLongRange, Player owner) { + if (!particleEffect.hasProperty(ParticleProperty.COLORABLE)) + throw new ParticleColorException("This particle effect is not colorable"); + + new ParticlePacket(particleEffect, color, true).sendTo(center, this.getPlayersInRange(center, isLongRange, owner)); + } + + @Override + public void display(ParticleEffect particleEffect, Material spawnMaterial, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + if (!particleEffect.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) + throw new ParticleDataException("This particle effect does not require additional data"); + + new ParticlePacket(particleEffect, offsetX, offsetY, offsetZ, speed, amount, true, spawnMaterial).sendTo(center, this.getPlayersInRange(center, isLongRange, owner)); + } + + /** + * 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, Esophose + * @since 1.5 + */ + public static final class ParticlePacket { + private static Class enumParticle; + private static Constructor packetConstructor; + private static Method getHandle; + private static Field playerConnection; + private static Method sendPacket; + static { + try { + if (NMSUtil.getVersionNumber() > 7) { + enumParticle = PackageType.MINECRAFT_SERVER.getClass("EnumParticle"); + } + Class packetClass = PackageType.MINECRAFT_SERVER.getClass(NMSUtil.getVersionNumber() < 7 ? "Packet63WorldParticles" : "PacketPlayOutWorldParticles"); + packetConstructor = ReflectionUtils.getConstructor(packetClass); + getHandle = ReflectionUtils.getMethod("CraftPlayer", PackageType.CRAFTBUKKIT_ENTITY, "getHandle"); + playerConnection = ReflectionUtils.getField("EntityPlayer", PackageType.MINECRAFT_SERVER, false, "playerConnection"); + sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", PackageType.MINECRAFT_SERVER.getClass("Packet")); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + private final ParticleEffect effect; + private float offsetX; + private final float offsetY; + private final float offsetZ; + private final float speed; + private final int amount; + private final boolean longDistance; + private final Material data; + private Object packet; + + /** + * Construct a new particle packet + * + * @param effect Particle 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 + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + * @param data Data of the effect + * @throws IllegalArgumentException If the speed or amount is lower than 0 + */ + public ParticlePacket(ParticleEffect effect, double offsetX, double offsetY, double offsetZ, double speed, int amount, boolean longDistance, Material data) throws IllegalArgumentException { + if (speed < 0) + throw new IllegalArgumentException("The speed is lower than 0"); + + if (amount < 0) + throw new IllegalArgumentException("The amount is lower than 0"); + + this.effect = effect; + this.offsetX = (float) offsetX; + this.offsetY = (float) offsetY; + this.offsetZ = (float) offsetZ; + this.speed = (float) speed; + this.amount = amount; + this.longDistance = longDistance; + this.data = data; + } + + /** + * Construct a new particle packet of a single colored particle + * + * @param effect Particle effect + * @param color Color of the particle + * @param longDistance Indicates whether the maximum distance is increased from 256 to 65536 + */ + public ParticlePacket(ParticleEffect effect, ParticleColor color, boolean longDistance) { + this(effect, color.getValueX(), color.getValueY(), color.getValueZ(), 1, 0, longDistance, null); + + if (effect == ParticleEffect.DUST && color instanceof OrdinaryColor && ((OrdinaryColor) color).getRed() == 0) + this.offsetX = Float.MIN_NORMAL; + } + + /** + * Initializes {@link #packet} with all set values + * + * @param center Center location of the effect + * @throws PacketInstantiationException If instantion fails due to an unknown error + */ + private void initializePacket(Location center) throws PacketInstantiationException { + if (this.packet != null) { + return; + } + try { + this.packet = packetConstructor.newInstance(); + if (NMSUtil.getVersionNumber() < 8) { + String name = ReflectiveParticleEffectMapping.valueOf(this.effect.name()).getName(); + if (this.data != null) { + name += getPacketDataString(this.data); + } + ReflectionUtils.setValue(this.packet, true, "a", name); + } else { + ReflectionUtils.setValue(this.packet, true, "a", enumParticle.getEnumConstants()[ReflectiveParticleEffectMapping.valueOf(this.effect.name()).getId()]); + ReflectionUtils.setValue(this.packet, true, "j", this.longDistance); + if (this.data != null) { + int[] packetData = getPacketData(this.data); + ReflectionUtils.setValue(this.packet, true, "k", this.effect == ParticleEffect.ITEM ? packetData : new int[] { packetData[0] | (packetData[1] << 12) }); + } + } + ReflectionUtils.setValue(this.packet, true, "b", (float) center.getX()); + ReflectionUtils.setValue(this.packet, true, "c", (float) center.getY()); + ReflectionUtils.setValue(this.packet, true, "d", (float) center.getZ()); + ReflectionUtils.setValue(this.packet, true, "e", this.offsetX); + ReflectionUtils.setValue(this.packet, true, "f", this.offsetY); + ReflectionUtils.setValue(this.packet, true, "g", this.offsetZ); + ReflectionUtils.setValue(this.packet, true, "h", this.speed); + ReflectionUtils.setValue(this.packet, true, "i", this.amount); + } catch (Exception exception) { + throw new PacketInstantiationException("Packet instantiation failed", exception); + } + } + + /** + * 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 + * @see #initializePacket(Location) + */ + public void sendTo(Location center, Player player) throws PacketInstantiationException, PacketSendingException { + this.initializePacket(center); + try { + sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), this.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) { + this.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; + } + this.sendTo(center, player); + } + } + + private static String getPacketDataString(Material data) { + int[] packetData = getPacketData(data); + return "_" + packetData[0] + "_" + packetData[1]; + } + + @SuppressWarnings("deprecation") + private static int[] getPacketData(Material data) { + return new int[] { data.getId(), 0 }; + } + + /** + * 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); + } + } + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/spawning/SpigotParticleSpawner.java b/src/main/java/dev/esophose/playerparticles/particles/spawning/SpigotParticleSpawner.java new file mode 100644 index 0000000..e91e2bc --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/spawning/SpigotParticleSpawner.java @@ -0,0 +1,65 @@ +package dev.esophose.playerparticles.particles.spawning; + +import dev.esophose.playerparticles.manager.ConfigurationManager.Setting; +import dev.esophose.playerparticles.particles.ParticleEffect; +import dev.esophose.playerparticles.particles.ParticleEffect.ParticleProperty; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; +import dev.esophose.playerparticles.particles.data.ParticleColor; +import dev.esophose.playerparticles.util.NMSUtil; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle.DustOptions; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; + +public class SpigotParticleSpawner extends ParticleSpawner { + + @Override + public void display(ParticleEffect particleEffect, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + if (particleEffect.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) + throw new ParticleDataException("This particle effect requires additional data"); + + for (Player player : this.getPlayersInRange(center, isLongRange, owner)) + player.spawnParticle(particleEffect.getSpigotEnum(), center.getX(), center.getY(), center.getZ(), amount, offsetX, offsetY, offsetZ, speed); + } + + @Override + public void display(ParticleEffect particleEffect, ParticleColor color, Location center, boolean isLongRange, Player owner) { + if (!particleEffect.hasProperty(ParticleProperty.COLORABLE)) + throw new ParticleColorException("This particle effect is not colorable"); + + if (particleEffect == ParticleEffect.DUST && NMSUtil.getVersionNumber() >= 13) { // DUST uses a special data object for spawning in 1.13+ + OrdinaryColor dustColor = (OrdinaryColor) color; + DustOptions dustOptions = new DustOptions(Color.fromRGB(dustColor.getRed(), dustColor.getGreen(), dustColor.getBlue()), Setting.DUST_SIZE.getFloat()); + for (Player player : this.getPlayersInRange(center, isLongRange, owner)) + player.spawnParticle(particleEffect.getSpigotEnum(), center.getX(), center.getY(), center.getZ(), 1, 0, 0, 0, 0, dustOptions); + } else { + for (Player player : this.getPlayersInRange(center, isLongRange, owner)) { + // Minecraft clients require that you pass a non-zero value if the Red value should be zero + player.spawnParticle(particleEffect.getSpigotEnum(), center.getX(), center.getY(), center.getZ(), 0, particleEffect == ParticleEffect.DUST && color.getValueX() == 0 ? Float.MIN_VALUE : color.getValueX(), color.getValueY(), color.getValueZ(), 1); + } + } + } + + @SuppressWarnings("deprecation") + @Override + public void display(ParticleEffect particleEffect, Material spawnMaterial, double offsetX, double offsetY, double offsetZ, double speed, int amount, Location center, boolean isLongRange, Player owner) { + if (!particleEffect.hasProperty(ParticleProperty.REQUIRES_MATERIAL_DATA)) + throw new ParticleDataException("This particle effect does not require additional data"); + + Object extraData = null; + if (particleEffect.getSpigotEnum().getDataType().getTypeName().equals("org.bukkit.block.data.BlockData")) { + extraData = spawnMaterial.createBlockData(); + } else if (particleEffect.getSpigotEnum().getDataType() == ItemStack.class) { + extraData = new ItemStack(spawnMaterial); + } else if (particleEffect.getSpigotEnum().getDataType() == MaterialData.class) { + extraData = new MaterialData(spawnMaterial); // Deprecated, only used in versions < 1.13 + } + + for (Player player : this.getPlayersInRange(center, isLongRange, owner)) + player.spawnParticle(particleEffect.getSpigotEnum(), center.getX(), center.getY(), center.getZ(), amount, offsetX, offsetY, offsetZ, speed, extraData); + } + +} diff --git a/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectionUtils.java b/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectionUtils.java new file mode 100644 index 0000000..6c2e70b --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectionUtils.java @@ -0,0 +1,649 @@ +package dev.esophose.playerparticles.particles.spawning.reflective; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Bukkit; + +/** + * ReflectionUtils + *

+ * This class provides useful methods which makes dealing with reflection much + * easier, especially when working with Bukkit + *

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

    + *
  • Don't claim this class as your own + *
  • Don't remove this disclaimer + *
+ *

+ * It would be nice if you provide credit to me if you use this class in a + * published project + * + * @author DarkBlade12 + * @version 1.1 + */ +public final class ReflectionUtils { + // Prevent accidental construction + private ReflectionUtils() { + + } + + /** + * Returns the constructor of a given class with the given parameter types + * + * @param clazz Target class + * @param parameterTypes Parameter types of the desired constructor + * @return The constructor of the target class with the specified parameter + * types + * @throws NoSuchMethodException If the desired constructor with the + * specified parameter types cannot be found + * @see DataType + * @see DataType#getPrimitive(Class[]) + * @see DataType#compare(Class[], Class[]) + */ + public static Constructor getConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException { + Class[] primitiveTypes = DataType.getPrimitive(parameterTypes); + for (Constructor constructor : clazz.getConstructors()) { + if (!DataType.compare(DataType.getPrimitive(constructor.getParameterTypes()), primitiveTypes)) { + continue; + } + return constructor; + } + throw new NoSuchMethodException("There is no such constructor in this class with the specified parameter types"); + } + + /** + * Returns the constructor of a desired class with the given parameter types + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param parameterTypes Parameter types of the desired constructor + * @return The constructor of the desired target class with the specified + * parameter types + * @throws NoSuchMethodException If the desired constructor with the + * specified parameter types cannot be found + * @throws ClassNotFoundException ClassNotFoundException If the desired + * target class with the specified name and package cannot be + * found + */ + public static Constructor getConstructor(String className, PackageType packageType, Class... parameterTypes) throws NoSuchMethodException, ClassNotFoundException { + return getConstructor(packageType.getClass(className), parameterTypes); + } + + /** + * Returns an instance of a class with the given arguments + * + * @param clazz Target class + * @param arguments Arguments which are used to construct an object of the + * target class + * @return The instance of the target class with the specified arguments + * @throws InstantiationException If you cannot create an instance of the + * target class due to certain circumstances + * @throws IllegalAccessException If the desired constructor cannot be + * accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not + * match the parameter types of the constructor (this should not + * occur since it searches for a constructor with the types of + * the arguments) + * @throws InvocationTargetException If the desired constructor cannot be + * invoked + * @throws NoSuchMethodException If the desired constructor with the + * specified arguments cannot be found + */ + public static Object instantiateObject(Class clazz, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getConstructor(clazz, DataType.getPrimitive(arguments)).newInstance(arguments); + } + + /** + * Returns an instance of a desired class with the given arguments + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param arguments Arguments which are used to construct an object of the + * desired target class + * @return The instance of the desired target class with the specified + * arguments + * @throws InstantiationException If you cannot create an instance of the + * desired target class due to certain circumstances + * @throws IllegalAccessException If the desired constructor cannot be + * accessed due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not + * match the parameter types of the constructor (this should not + * occur since it searches for a constructor with the types of + * the arguments) + * @throws InvocationTargetException If the desired constructor cannot be + * invoked + * @throws NoSuchMethodException If the desired constructor with the + * specified arguments cannot be found + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + */ + public static Object instantiateObject(String className, PackageType packageType, Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + return instantiateObject(packageType.getClass(className), arguments); + } + + /** + * Returns a method of a class with the given parameter types + * + * @param clazz Target class + * @param methodName Name of the desired method + * @param parameterTypes Parameter types of the desired method + * @return The method of the target class with the specified name and + * parameter types + * @throws NoSuchMethodException If the desired method of the target class + * with the specified name and parameter types cannot be found + * @see DataType#getPrimitive(Class[]) + * @see DataType#compare(Class[], Class[]) + */ + public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException { + Class[] primitiveTypes = DataType.getPrimitive(parameterTypes); + for (Method method : clazz.getMethods()) { + if (!method.getName().equals(methodName) || !DataType.compare(DataType.getPrimitive(method.getParameterTypes()), primitiveTypes)) { + continue; + } + return method; + } + throw new NoSuchMethodException("There is no such method in this class with the specified name and parameter types"); + } + + /** + * Returns a method of a desired class with the given parameter types + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param methodName Name of the desired method + * @param parameterTypes Parameter types of the desired method + * @return The method of the desired target class with the specified name + * and parameter types + * @throws NoSuchMethodException If the desired method of the desired target + * class with the specified name and parameter types cannot be + * found + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + */ + public static Method getMethod(String className, PackageType packageType, String methodName, Class... parameterTypes) throws NoSuchMethodException, ClassNotFoundException { + return getMethod(packageType.getClass(className), methodName, parameterTypes); + } + + /** + * Invokes a method on an object with the given arguments + * + * @param instance Target object + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed + * due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not + * match the parameter types of the method (this should not + * occur since it searches for a method with the types of the + * arguments) + * @throws InvocationTargetException If the desired method cannot be invoked + * on the target object + * @throws NoSuchMethodException If the desired method of the class of the + * target object with the specified name and arguments cannot be + * found + */ + public static Object invokeMethod(Object instance, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getMethod(instance.getClass(), methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments); + } + + /** + * Invokes a method of the target class on an object with the given + * arguments + * + * @param instance Target object + * @param clazz Target class + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed + * due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not + * match the parameter types of the method (this should not + * occur since it searches for a method with the types of the + * arguments) + * @throws InvocationTargetException If the desired method cannot be invoked + * on the target object + * @throws NoSuchMethodException If the desired method of the target class + * with the specified name and arguments cannot be found + * @see #getMethod(Class, String, Class...) + * @see DataType#getPrimitive(Object[]) + */ + public static Object invokeMethod(Object instance, Class clazz, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return getMethod(clazz, methodName, DataType.getPrimitive(arguments)).invoke(instance, arguments); + } + + /** + * Invokes a method of a desired class on an object with the given arguments + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param methodName Name of the desired method + * @param arguments Arguments which are used to invoke the desired method + * @return The result of invoking the desired method on the target object + * @throws IllegalAccessException If the desired method cannot be accessed + * due to certain circumstances + * @throws IllegalArgumentException If the types of the arguments do not + * match the parameter types of the method (this should not + * occur since it searches for a method with the types of the + * arguments) + * @throws InvocationTargetException If the desired method cannot be invoked + * on the target object + * @throws NoSuchMethodException If the desired method of the desired target + * class with the specified name and arguments cannot be found + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + */ + public static Object invokeMethod(Object instance, String className, PackageType packageType, String methodName, Object... arguments) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + return invokeMethod(instance, packageType.getClass(className), methodName, arguments); + } + + /** + * Returns a field of the target class with the given name + * + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The field of the target class with the specified name + * @throws NoSuchFieldException If the desired field of the given class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + */ + public static Field getField(Class clazz, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException { + Field field = declared ? clazz.getDeclaredField(fieldName) : clazz.getField(fieldName); + field.setAccessible(true); + return field; + } + + /** + * Returns a field of a desired class with the given name + * + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The field of the desired target class with the specified name + * @throws NoSuchFieldException If the desired field of the desired class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + * @see #getField(Class, boolean, String) + */ + public static Field getField(String className, PackageType packageType, boolean declared, String fieldName) throws NoSuchFieldException, SecurityException, ClassNotFoundException { + return getField(packageType.getClass(className), declared, fieldName); + } + + /** + * Returns the value of a field of the given class of an object + * + * @param instance Target object + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature + * the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getField(Class, boolean, String) + */ + public static Object getValue(Object instance, Class clazz, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + return getField(clazz, declared, fieldName).get(instance); + } + + /** + * Returns the value of a field of a desired class of an object + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature + * the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the desired class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + * @see #getValue(Object, Class, boolean, String) + */ + public static Object getValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException { + return getValue(instance, packageType.getClass(className), declared, fieldName); + } + + /** + * Returns the value of a field with the given name of an object + * + * @param instance Target object + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @return The value of field of the target object + * @throws IllegalArgumentException If the target object does not feature + * the desired field (should not occur since it searches for a + * field with the given name in the class of the object) + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target object + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getValue(Object, Class, boolean, String) + */ + public static Object getValue(Object instance, boolean declared, String fieldName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + return getValue(instance, instance.getClass(), declared, fieldName); + } + + /** + * Sets the value of a field of the given class of an object + * + * @param instance Target object + * @param clazz Target class + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match + * the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #getField(Class, boolean, String) + */ + public static void setValue(Object instance, Class clazz, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + getField(clazz, declared, fieldName).set(instance, value); + } + + /** + * Sets the value of a field of a desired class of an object + * + * @param instance Target object + * @param className Name of the desired target class + * @param packageType Package where the desired target class is located + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match + * the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the desired class + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @throws ClassNotFoundException If the desired target class with the + * specified name and package cannot be found + * @see #setValue(Object, Class, boolean, String, Object) + */ + public static void setValue(Object instance, String className, PackageType packageType, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, ClassNotFoundException { + setValue(instance, packageType.getClass(className), declared, fieldName, value); + } + + /** + * Sets the value of a field with the given name of an object + * + * @param instance Target object + * @param declared Whether the desired field is declared or not + * @param fieldName Name of the desired field + * @param value New value + * @throws IllegalArgumentException If the type of the value does not match + * the type of the desired field + * @throws IllegalAccessException If the desired field cannot be accessed + * @throws NoSuchFieldException If the desired field of the target object + * cannot be found + * @throws SecurityException If the desired field cannot be made accessible + * @see #setValue(Object, Class, boolean, String, Object) + */ + public static void setValue(Object instance, boolean declared, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + setValue(instance, instance.getClass(), declared, fieldName, value); + } + + /** + * Represents an enumeration of dynamic packages of NMS and CraftBukkit + *

+ * This class is part of the ReflectionUtils and follows the same + * usage conditions + * + * @author DarkBlade12 + * @since 1.0 + */ + public enum PackageType { + MINECRAFT_SERVER("net.minecraft.server." + getServerVersion()), CRAFTBUKKIT("org.bukkit.craftbukkit." + getServerVersion()), CRAFTBUKKIT_BLOCK(CRAFTBUKKIT, "block"), CRAFTBUKKIT_CHUNKIO(CRAFTBUKKIT, "chunkio"), CRAFTBUKKIT_COMMAND(CRAFTBUKKIT, "command"), CRAFTBUKKIT_CONVERSATIONS(CRAFTBUKKIT, "conversations"), CRAFTBUKKIT_ENCHANTMENS(CRAFTBUKKIT, "enchantments"), CRAFTBUKKIT_ENTITY(CRAFTBUKKIT, "entity"), CRAFTBUKKIT_EVENT(CRAFTBUKKIT, "event"), CRAFTBUKKIT_GENERATOR(CRAFTBUKKIT, "generator"), CRAFTBUKKIT_HELP(CRAFTBUKKIT, "help"), CRAFTBUKKIT_INVENTORY(CRAFTBUKKIT, "inventory"), CRAFTBUKKIT_MAP(CRAFTBUKKIT, "map"), CRAFTBUKKIT_METADATA(CRAFTBUKKIT, "metadata"), CRAFTBUKKIT_POTION(CRAFTBUKKIT, "potion"), CRAFTBUKKIT_PROJECTILES(CRAFTBUKKIT, "projectiles"), CRAFTBUKKIT_SCHEDULER(CRAFTBUKKIT, "scheduler"), CRAFTBUKKIT_SCOREBOARD(CRAFTBUKKIT, "scoreboard"), CRAFTBUKKIT_UPDATER(CRAFTBUKKIT, "updater"), CRAFTBUKKIT_UTIL(CRAFTBUKKIT, "util"); + + private final String path; + + /** + * Construct a new package type + * + * @param path Path of the package + */ + private PackageType(String path) { + this.path = path; + } + + /** + * Construct a new package type + * + * @param parent Parent package of the package + * @param path Path of the package + */ + private PackageType(PackageType parent, String path) { + this(parent + "." + path); + } + + /** + * Returns the path of this package type + * + * @return The path + */ + public String getPath() { + return path; + } + + /** + * Returns the class with the given name + * + * @param className Name of the desired class + * @return The class with the specified name + * @throws ClassNotFoundException If the desired class with the + * specified name and package cannot be found + */ + public Class getClass(String className) throws ClassNotFoundException { + return Class.forName(this + "." + className); + } + + // Override for convenience + @Override + public String toString() { + return path; + } + + /** + * Returns the version of your server + * + * @return The server version + */ + public static String getServerVersion() { + return Bukkit.getServer().getClass().getPackage().getName().substring(23); + } + } + + /** + * Represents an enumeration of Java data types with corresponding classes + *

+ * This class is part of the ReflectionUtils and follows the same + * usage conditions + * + * @author DarkBlade12 + * @since 1.0 + */ + public enum DataType { + BYTE(byte.class, Byte.class), SHORT(short.class, Short.class), INTEGER(int.class, Integer.class), LONG(long.class, Long.class), CHARACTER(char.class, Character.class), FLOAT(float.class, Float.class), DOUBLE(double.class, Double.class), BOOLEAN(boolean.class, Boolean.class); + + private static final Map, DataType> CLASS_MAP = new HashMap, DataType>(); + private final Class primitive; + private final Class reference; + + // Initialize map for quick class lookup + static { + for (DataType type : values()) { + CLASS_MAP.put(type.primitive, type); + CLASS_MAP.put(type.reference, type); + } + } + + /** + * Construct a new data type + * + * @param primitive Primitive class of this data type + * @param reference Reference class of this data type + */ + private DataType(Class primitive, Class reference) { + this.primitive = primitive; + this.reference = reference; + } + + /** + * Returns the primitive class of this data type + * + * @return The primitive class + */ + public Class getPrimitive() { + return primitive; + } + + /** + * Returns the reference class of this data type + * + * @return The reference class + */ + public Class getReference() { + return reference; + } + + /** + * Returns the data type with the given primitive/reference class + * + * @param clazz Primitive/Reference class of the data type + * @return The data type + */ + public static DataType fromClass(Class clazz) { + return CLASS_MAP.get(clazz); + } + + /** + * Returns the primitive class of the data type with the given reference + * class + * + * @param clazz Reference class of the data type + * @return The primitive class + */ + public static Class getPrimitive(Class clazz) { + DataType type = fromClass(clazz); + return type == null ? clazz : type.getPrimitive(); + } + + /** + * Returns the reference class of the data type with the given primitive + * class + * + * @param clazz Primitive class of the data type + * @return The reference class + */ + public static Class getReference(Class clazz) { + DataType type = fromClass(clazz); + return type == null ? clazz : type.getReference(); + } + + /** + * Returns the primitive class array of the given class array + * + * @param classes Given class array + * @return The primitive class array + */ + public static Class[] getPrimitive(Class[] classes) { + int length = classes == null ? 0 : classes.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getPrimitive(classes[index]); + } + return types; + } + + /** + * Returns the reference class array of the given class array + * + * @param classes Given class array + * @return The reference class array + */ + public static Class[] getReference(Class[] classes) { + int length = classes == null ? 0 : classes.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getReference(classes[index]); + } + return types; + } + + /** + * Returns the primitive class array of the given object array + * + * @param objects Given object array + * @return The primitive class array + */ + public static Class[] getPrimitive(Object[] objects) { + int length = objects == null ? 0 : objects.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getPrimitive(objects[index].getClass()); + } + return types; + } + + /** + * Returns the reference class array of the given object array + * + * @param objects Given object array + * @return The reference class array + */ + public static Class[] getReference(Object[] objects) { + int length = objects == null ? 0 : objects.length; + Class[] types = new Class[length]; + for (int index = 0; index < length; index++) { + types[index] = getReference(objects[index].getClass()); + } + return types; + } + + /** + * Compares two class arrays on equivalence + * + * @param primary Primary class array + * @param secondary Class array which is compared to the primary array + * @return Whether these arrays are equal or not + */ + public static boolean compare(Class[] primary, Class[] secondary) { + if (primary == null || secondary == null || primary.length != secondary.length) { + return false; + } + for (int index = 0; index < primary.length; index++) { + Class primaryClass = primary[index]; + Class secondaryClass = secondary[index]; + if (primaryClass.equals(secondaryClass) || primaryClass.isAssignableFrom(secondaryClass)) { + continue; + } + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectiveParticleEffectMapping.java b/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectiveParticleEffectMapping.java new file mode 100644 index 0000000..87d7ae6 --- /dev/null +++ b/src/main/java/dev/esophose/playerparticles/particles/spawning/reflective/ReflectiveParticleEffectMapping.java @@ -0,0 +1,84 @@ +package dev.esophose.playerparticles.particles.spawning.reflective; + +import dev.esophose.playerparticles.util.NMSUtil; + +public enum ReflectiveParticleEffectMapping { + + POOF("explode", 0), + EXPLOSION("largeexplode", 1), + EXPLOSION_EMITTER("hugeexplosion", 2), + FIREWORK("fireworksSpark", 3), + BUBBLE("bubble", 4), + SPLASH("splash", 5), + FISHING("wake", 6), + //SUSPENDED("suspended", 7), + MYCELIUM("depthSuspend", 8), + CRIT("crit", 9), + ENCHANTED_HIT("magicCrit", 10), + SMOKE("smoke", 11), + LARGE_SMOKE("largesmoke", 12), + SPELL("spell", 13), + INSTANT_EFFECT("instantSpell", 14), + ENTITY_EFFECT("mobSpell", 15), + AMBIENT_ENTITY_EFFECT("mobSpellAmbient", 16), + WITCH("witchMagic", 17), + DRIPPING_WATER("dripWater", 18), + DRIPPING_LAVA("dripLava", 19), + ANGRY_VILLAGER("angryVillager", 20), + HAPPY_VILLAGER("happyVillager", 21), + NOTE("note", 23), + PORTAL("portal", 24), + ENCHANT("enchantmenttable", 25), + FLAME("flame", 26), + LAVA("lava", 27), + FOOTSTEP("footstep", 28), + CLOUD("cloud", 29), + DUST("reddust", 30), + ITEM_SNOWBALL("snowballpoof", 31), + //SNOW_SHOVEL("snowshovel", 32), + ITEM_SLIME("slime", 33), + HEART("heart", 34), + BARRIER("barrier", 35, 8), + ITEM("iconcrack", 36), + BLOCK("blockcrack", 37), + //BLOCK_DUST("blockdust", 38), + RAIN("droplet", 39, 8), + //ITEM_TAKE("take", 40, 8), + ELDER_GUARDIAN("mobappearance", 41, 8); + + private final String name; + private final int id; + private final boolean supported; + + ReflectiveParticleEffectMapping(String name, int id, int requiredVersion) { + this.name = name; + this.id = id; + this.supported = NMSUtil.getVersionNumber() >= requiredVersion; + } + + ReflectiveParticleEffectMapping(String name, int id) { + this(name, id, -1); + } + + /** + * @return the name of the internal minecraft particle effect + */ + public String getName() { + return this.name; + } + + /** + * @return the id of the internal minecraft particle effect + */ + public int getId() { + return this.id; + } + + /** + * @return true if this particle effect is supported, otherwise false + */ + public boolean isSupported() { + return this.supported; + } + +} \ No newline at end of file diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyle.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyle.java index bf16bc8..38204d0 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyle.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyle.java @@ -4,6 +4,7 @@ import dev.esophose.playerparticles.PlayerParticles; import dev.esophose.playerparticles.manager.ParticleStyleManager; import dev.esophose.playerparticles.particles.PParticle; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.util.ParticleUtils; import java.util.Collections; import java.util.List; import org.bukkit.Location; @@ -48,7 +49,7 @@ public interface ParticleStyle { * @return The Material icon that represents this style in the GUI */ default Material getGuiIconMaterial() { - return Material.BARRIER; + return ParticleUtils.FALLBACK_MATERIAL; } /** diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleCelebration.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleCelebration.java index 2c63ec6..4ba1665 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleCelebration.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleCelebration.java @@ -10,6 +10,7 @@ import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; import dev.esophose.playerparticles.particles.ParticlePair; import dev.esophose.playerparticles.util.MathL; +import dev.esophose.playerparticles.util.NMSUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -60,7 +61,7 @@ public class ParticleStyleCelebration extends DefaultParticleStyle { Random random = new Random(); for (PPlayer pplayer : particleManager.getPPlayers()) { Player player = pplayer.getPlayer(); - if (player != null && player.getGameMode() != GameMode.SPECTATOR && permissionManager.isWorldEnabled(player.getWorld().getName())) + if (player != null && (NMSUtil.getVersionNumber() < 8 || player.getGameMode() != GameMode.SPECTATOR) && permissionManager.isWorldEnabled(player.getWorld().getName())) for (ParticlePair particle : pplayer.getActiveParticles()) if (particle.getStyle() == this) this.spawnFirework(player.getLocation(), pplayer, pplayer.getPlayer(), particle, random); diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStylePopper.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStylePopper.java index 1b65285..448d0cb 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStylePopper.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStylePopper.java @@ -54,7 +54,7 @@ public class ParticleStylePopper extends DefaultParticleStyle { @Override protected List getGuiIconMaterialNames() { - return Arrays.asList("POPPED_CHORUS_FRUIT", "CHORUS_FRUIT_POPPED"); + return Arrays.asList("POPPED_CHORUS_FRUIT", "CHORUS_FRUIT_POPPED", "PUMPKIN_SEEDS"); } @Override diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleSwords.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleSwords.java index 88cefd5..db1dd8f 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleSwords.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleSwords.java @@ -7,6 +7,7 @@ import dev.esophose.playerparticles.manager.ParticleManager; import dev.esophose.playerparticles.particles.PParticle; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticlePair; +import dev.esophose.playerparticles.util.NMSUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -76,9 +77,17 @@ public class ParticleStyleSwords extends DefaultParticleStyle implements Listene Player player = (Player) event.getDamager(); LivingEntity entity = (LivingEntity) event.getEntity(); PPlayer pplayer = PlayerParticles.getInstance().getManager(DataManager.class).getPPlayer(player.getUniqueId()); - if (pplayer == null || !this.isSword(player.getInventory().getItemInMainHand())) + if (pplayer == null) return; + if (NMSUtil.getVersionNumber() > 8) { + if (!this.isSword(player.getInventory().getItemInMainHand())) + return; + } else { + if (!this.isSword(player.getInventory().getItemInHand())) + return; + } + for (ParticlePair particle : pplayer.getActiveParticlesForStyle(DefaultStyles.SWORDS)) { Location loc = entity.getLocation().clone().add(0, 1, 0); particleManager.displayParticles(player, player.getWorld(), particle, DefaultStyles.SWORDS.getParticles(particle, loc), false); diff --git a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleWings.java b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleWings.java index e160c64..089b2f1 100644 --- a/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleWings.java +++ b/src/main/java/dev/esophose/playerparticles/styles/ParticleStyleWings.java @@ -6,6 +6,7 @@ import dev.esophose.playerparticles.particles.ParticlePair; import dev.esophose.playerparticles.util.MathL; import dev.esophose.playerparticles.util.VectorUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.bukkit.Location; @@ -44,7 +45,7 @@ public class ParticleStyleWings extends DefaultParticleStyle { @Override protected List getGuiIconMaterialNames() { - return Collections.singletonList("ELYTRA"); + return Arrays.asList("ELYTRA", "RAW_CHICKEN"); } @Override diff --git a/src/main/java/dev/esophose/playerparticles/util/Metrics.java b/src/main/java/dev/esophose/playerparticles/util/LegacyMetrics.java similarity index 74% rename from src/main/java/dev/esophose/playerparticles/util/Metrics.java rename to src/main/java/dev/esophose/playerparticles/util/LegacyMetrics.java index eda8d5a..de8b83f 100644 --- a/src/main/java/dev/esophose/playerparticles/util/Metrics.java +++ b/src/main/java/dev/esophose/playerparticles/util/LegacyMetrics.java @@ -1,8 +1,5 @@ package dev.esophose.playerparticles.util; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -27,6 +24,8 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; /** * bStats collects some data for plugin authors. @@ -34,7 +33,7 @@ import org.bukkit.plugin.ServicePriority; * Check out https://bStats.org/ to learn more about bStats! */ @SuppressWarnings({"WeakerAccess", "unused"}) -public class Metrics { +public class LegacyMetrics { static { // You can use the property to disable the check in your test environment @@ -44,7 +43,7 @@ public class Metrics { new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + if (LegacyMetrics.class.getPackage().getName().equals(defaultPackage) || LegacyMetrics.class.getPackage().getName().equals(examplePackage)) { throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); } } @@ -79,7 +78,7 @@ public class Metrics { * * @param plugin The plugin which stats should be submitted. */ - public Metrics(Plugin plugin) { + public LegacyMetrics(Plugin plugin) { if (plugin == null) { throw new IllegalArgumentException("Plugin cannot be null!"); } @@ -119,10 +118,8 @@ public class Metrics { // Load the data serverUUID = config.getString("serverUuid"); logFailedRequests = config.getBoolean("logFailedRequests", false); - this.enabled = config.getBoolean("enabled", true); - logSentData = config.getBoolean("logSentData", false); - logResponseStatusText = config.getBoolean("logResponseStatusText", false); - if (this.enabled) { + enabled = config.getBoolean("enabled", true); + if (enabled) { boolean found = false; // Search for all other bStats Metrics classes to see if we are the first one for (Class service : Bukkit.getServicesManager().getKnownServices()) { @@ -133,10 +130,10 @@ public class Metrics { } catch (NoSuchFieldException ignored) { } } // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + Bukkit.getServicesManager().register(LegacyMetrics.class, this, plugin, ServicePriority.Normal); if (!found) { // We are the first! - this.startSubmitting(); + startSubmitting(); } } } @@ -147,7 +144,7 @@ public class Metrics { * @return Whether bStats is enabled or not. */ public boolean isEnabled() { - return this.enabled; + return enabled; } /** @@ -158,13 +155,13 @@ public class Metrics { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { - if (!Metrics.this.plugin.isEnabled()) { // Plugin was disabled + if (!plugin.isEnabled()) { // Plugin was disabled timer.cancel(); return; } // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(Metrics.this.plugin, Metrics.this::submitData); + Bukkit.getScheduler().runTask(plugin, () -> submitData()); } }, 1000 * 60 * 5, 1000 * 60 * 30); // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start @@ -178,15 +175,16 @@ public class Metrics { * * @return The plugin specific data. */ - public JsonObject getPluginData() { - JsonObject data = new JsonObject(); + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); - String pluginName = this.plugin.getDescription().getName(); - String pluginVersion = this.plugin.getDescription().getVersion(); + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); - data.addProperty("pluginName", pluginName); // Append the name of the plugin - data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin - data.add("customCharts", new JsonArray()); + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + data.put("customCharts", customCharts); return data; } @@ -196,7 +194,7 @@ public class Metrics { * * @return The server specific data. */ - private JsonObject getServerData() { + private JSONObject getServerData() { // Minecraft specific data int playerAmount; try { @@ -211,7 +209,6 @@ public class Metrics { } int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; String bukkitVersion = Bukkit.getVersion(); - String bukkitName = Bukkit.getName(); // OS/Java specific data String javaVersion = System.getProperty("java.version"); @@ -220,20 +217,19 @@ public class Metrics { String osVersion = System.getProperty("os.version"); int coreCount = Runtime.getRuntime().availableProcessors(); - JsonObject data = new JsonObject(); + JSONObject data = new JSONObject(); - data.addProperty("serverUUID", serverUUID); + data.put("serverUUID", serverUUID); - data.addProperty("playerAmount", playerAmount); - data.addProperty("onlineMode", onlineMode); - data.addProperty("bukkitVersion", bukkitVersion); - data.addProperty("bukkitName", bukkitName); + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); - data.addProperty("javaVersion", javaVersion); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); return data; } @@ -242,9 +238,9 @@ public class Metrics { * Collects the data and sends it afterwards. */ private void submitData() { - final JsonObject data = this.getServerData(); + final JSONObject data = getServerData(); - JsonArray pluginData = new JsonArray(); + JSONArray pluginData = new JSONArray(); // Search for all other bStats Metrics classes to get their plugin data for (Class service : Bukkit.getServicesManager().getKnownServices()) { try { @@ -252,43 +248,27 @@ public class Metrics { for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { try { - Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); - if (plugin instanceof JsonObject) { - pluginData.add((JsonObject) plugin); - } else { // old bstats version compatibility - try { - Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); - if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { - Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); - jsonStringGetter.setAccessible(true); - String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); - pluginData.add(object); - } - } catch (ClassNotFoundException e) { - // minecraft version 1.14+ - if (logFailedRequests) { - this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception ", e); - } - } - } + pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } } } catch (NoSuchFieldException ignored) { } } - data.add("plugins", pluginData); + data.put("plugins", pluginData); // Create a new thread for the connection to the bStats server - new Thread(() -> { - try { - // Send the data - sendData(this.plugin, data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - this.plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + this.plugin.getName(), e); + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } } } }).start(); @@ -301,7 +281,7 @@ public class Metrics { * @param data The data to send. * @throws Exception If the request failed. */ - private static void sendData(Plugin plugin, JsonObject data) throws Exception { + private static void sendData(Plugin plugin, JSONObject data) throws Exception { if (data == null) { throw new IllegalArgumentException("Data cannot be null!"); } diff --git a/src/main/java/dev/esophose/playerparticles/util/NMSUtil.java b/src/main/java/dev/esophose/playerparticles/util/NMSUtil.java index 6267048..2351163 100644 --- a/src/main/java/dev/esophose/playerparticles/util/NMSUtil.java +++ b/src/main/java/dev/esophose/playerparticles/util/NMSUtil.java @@ -33,4 +33,16 @@ public final class NMSUtil { return cachedVersionNumber; } + /** + * @return true if the server is running Spigot or a fork, false otherwise + */ + public static boolean isSpigot() { + try { + Class.forName("org.spigotmc.SpigotConfig"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + } diff --git a/src/main/java/dev/esophose/playerparticles/util/ParticleUtils.java b/src/main/java/dev/esophose/playerparticles/util/ParticleUtils.java index daaa99f..4200c97 100644 --- a/src/main/java/dev/esophose/playerparticles/util/ParticleUtils.java +++ b/src/main/java/dev/esophose/playerparticles/util/ParticleUtils.java @@ -1,15 +1,25 @@ package dev.esophose.playerparticles.util; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.apache.commons.lang.WordUtils; +import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.entity.Player; public final class ParticleUtils { - + + public final static Material FALLBACK_MATERIAL; private static List blockMaterials, itemMaterials; static { + if (NMSUtil.getVersionNumber() > 7) { + FALLBACK_MATERIAL = Material.BARRIER; + } else { + FALLBACK_MATERIAL = Material.BEDROCK; + } + blockMaterials = new ArrayList<>(); itemMaterials = new ArrayList<>(); @@ -43,19 +53,19 @@ public final class ParticleUtils { * Finds a block/item as a material from a list of possible strings * Contains a fallback to the barrier icon just in case * - * @param barrierFallback If the material should fall back to barrier + * @param fallback If the material should fall back to barrier * @param input A list of material names * @return The first matching material */ - public static Material closestMatchWithFallback(boolean barrierFallback, String... input) { + public static Material closestMatchWithFallback(boolean fallback, String... input) { for (String name : input) { Material mat = closestMatch(name); if (mat != null) return mat; } - if (barrierFallback) - return Material.BARRIER; + if (fallback) + return FALLBACK_MATERIAL; return null; } diff --git a/src/main/java/dev/esophose/playerparticles/util/inputparser/InputParser.java b/src/main/java/dev/esophose/playerparticles/util/inputparser/InputParser.java index f947d85..b8c8acf 100644 --- a/src/main/java/dev/esophose/playerparticles/util/inputparser/InputParser.java +++ b/src/main/java/dev/esophose/playerparticles/util/inputparser/InputParser.java @@ -2,8 +2,8 @@ package dev.esophose.playerparticles.util.inputparser; import dev.esophose.playerparticles.particles.PPlayer; import dev.esophose.playerparticles.particles.ParticleEffect; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; +import dev.esophose.playerparticles.particles.data.NoteColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.styles.ParticleStyle; import dev.esophose.playerparticles.util.inputparser.parsable.ParsableInteger; import dev.esophose.playerparticles.util.inputparser.parsable.ParsableLocation; diff --git a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableLocation.java b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableLocation.java index 626ee1a..9e3f291 100644 --- a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableLocation.java +++ b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableLocation.java @@ -1,7 +1,11 @@ package dev.esophose.playerparticles.util.inputparser.parsable; import dev.esophose.playerparticles.particles.PPlayer; +import dev.esophose.playerparticles.util.NMSUtil; import dev.esophose.playerparticles.util.inputparser.Parsable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.bukkit.Bukkit; @@ -9,10 +13,22 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; public class ParsableLocation extends Parsable { + private static Method LivingEntity_getTargetBlock; + static { + if (NMSUtil.getVersionNumber() < 8) { + try { + LivingEntity_getTargetBlock = LivingEntity.class.getDeclaredMethod("getTargetBlock", HashSet.class, int.class); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + } + public ParsableLocation() { super(Location.class); } @@ -23,7 +39,18 @@ public class ParsableLocation extends Parsable { Player player = pplayer.getPlayer(); if (player != null && input.equalsIgnoreCase("looking")) { - Block targetBlock = player.getTargetBlock((Set) null, 8); // Need the Set cast for 1.9 support + Block targetBlock; + if (NMSUtil.getVersionNumber() > 7) { + targetBlock = player.getTargetBlock((Set) null, 8); // Need the Set cast for 1.9 support + } else { + try { + targetBlock = (Block) LivingEntity_getTargetBlock.invoke(player, null, 8); + } catch (ReflectiveOperationException e) { + targetBlock = player.getLocation().getBlock(); + e.printStackTrace(); + } + } + int maxDistanceSqrd = 6 * 6; if (targetBlock.getLocation().distanceSquared(player.getLocation()) > maxDistanceSqrd) return null; // Looking at a block too far away diff --git a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableNoteColor.java b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableNoteColor.java index 8d17dd7..834e189 100644 --- a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableNoteColor.java +++ b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableNoteColor.java @@ -1,7 +1,7 @@ package dev.esophose.playerparticles.util.inputparser.parsable; import dev.esophose.playerparticles.particles.PPlayer; -import dev.esophose.playerparticles.particles.ParticleEffect.NoteColor; +import dev.esophose.playerparticles.particles.data.NoteColor; import dev.esophose.playerparticles.util.inputparser.Parsable; import java.util.List; diff --git a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableOrdinaryColor.java b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableOrdinaryColor.java index 84286cd..f7205a7 100644 --- a/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableOrdinaryColor.java +++ b/src/main/java/dev/esophose/playerparticles/util/inputparser/parsable/ParsableOrdinaryColor.java @@ -1,7 +1,7 @@ package dev.esophose.playerparticles.util.inputparser.parsable; import dev.esophose.playerparticles.particles.PPlayer; -import dev.esophose.playerparticles.particles.ParticleEffect.OrdinaryColor; +import dev.esophose.playerparticles.particles.data.OrdinaryColor; import dev.esophose.playerparticles.util.inputparser.Parsable; import java.awt.Color; import java.util.Collections; diff --git a/src/main/resources/preset_groups.yml b/src/main/resources/preset_groups.yml index 93fff39..db88f43 100644 --- a/src/main/resources/preset_groups.yml +++ b/src/main/resources/preset_groups.yml @@ -51,7 +51,7 @@ raincloud: style: 'overhead' data: '' 2: - effect: 'rain' + effect: 'dripping_water' style: 'overhead' data: '' rainbows: