From a95106a335bc63f594984b9cf215818aae261520 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 8 Aug 2016 21:55:32 +0200 Subject: [PATCH] Client-side shop items All shop items are now spawned with packets and reflection client-side, so probably duplicated items are history (finally). This also allowed me to remove the ClearLag and LWC dependency, as ClearLag can't remove client-side items and LWC's Magnet Sucker can't suck them inside a chest. I also changed a bit in the classes of the nms package, so all required classes have to be found before attempting to do anything. Fixes #11 and fixes #4 --- pom.xml | 17 -- .../java/de/epiceric/shopchest/ShopChest.java | 55 +------ .../shopchest/listeners/ClearLagListener.java | 25 --- .../listeners/ItemCustomNameListener.java | 17 -- .../listeners/ItemProtectListener.java | 134 --------------- .../listeners/LWCMagnetListener.java | 39 ----- .../listeners/NotifyUpdateOnJoinListener.java | 2 +- .../shopchest/listeners/ShopItemListener.java | 126 ++++++++++++++ .../de/epiceric/shopchest/nms/Hologram.java | 41 ++--- .../epiceric/shopchest/nms/JsonBuilder.java | 35 ++-- .../epiceric/shopchest/nms/SpawnEggMeta.java | 9 +- .../java/de/epiceric/shopchest/shop/Shop.java | 35 ++-- .../de/epiceric/shopchest/shop/ShopItem.java | 155 ++++++++++++++++++ .../epiceric/shopchest/utils/ShopUtils.java | 30 ---- .../de/epiceric/shopchest/utils/Utils.java | 24 +++ 15 files changed, 372 insertions(+), 372 deletions(-) delete mode 100644 src/main/java/de/epiceric/shopchest/listeners/ClearLagListener.java delete mode 100644 src/main/java/de/epiceric/shopchest/listeners/ItemCustomNameListener.java delete mode 100644 src/main/java/de/epiceric/shopchest/listeners/ItemProtectListener.java delete mode 100644 src/main/java/de/epiceric/shopchest/listeners/LWCMagnetListener.java create mode 100644 src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java create mode 100644 src/main/java/de/epiceric/shopchest/shop/ShopItem.java diff --git a/pom.xml b/pom.xml index 8e441d4..38fac82 100644 --- a/pom.xml +++ b/pom.xml @@ -24,10 +24,6 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - shopchest-repo - https://epicericee.github.io/ShopChest/maven/ - vault-repo http://nexus.hc.to/content/repositories/pub_releases/ @@ -41,25 +37,12 @@ 1.10.2-R0.1-SNAPSHOT provided - net.milkbowl.vault VaultAPI 1.6 provided - - me.minebuilders - clearlag - 2.9.1 - provided - - - com.griefcraft.lwc - lwc-entity-locking - 1.7.3 - provided - diff --git a/src/main/java/de/epiceric/shopchest/ShopChest.java b/src/main/java/de/epiceric/shopchest/ShopChest.java index b1c8618..073e1b9 100644 --- a/src/main/java/de/epiceric/shopchest/ShopChest.java +++ b/src/main/java/de/epiceric/shopchest/ShopChest.java @@ -22,9 +22,6 @@ import de.epiceric.shopchest.utils.Utils; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; @@ -41,7 +38,6 @@ public class ShopChest extends JavaPlugin { private Config config = null; private Economy econ = null; private Permission perm = null; - private boolean lwc = false; private Database database; private boolean isUpdateNeeded = false; private String latestVersion = ""; @@ -238,8 +234,6 @@ public class ShopChest extends JavaPlugin { }, config.auto_reload_time * 20, config.auto_reload_time * 20); } - lwc = getServer().getPluginManager().isPluginEnabled("LWC"); - Bukkit.getScheduler().runTaskAsynchronously(this, new Runnable() { @Override public void run() { @@ -254,7 +248,7 @@ public class ShopChest extends JavaPlugin { Bukkit.getConsoleSender().sendMessage("[ShopChest] " + LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, latestVersion))); for (Player p : getServer().getOnlinePlayers()) { - if (p.isOp() || perm.has(p, "shopchest.notification.update")) { + if (perm.has(p, "shopchest.notification.update")) { JsonBuilder jb = new JsonBuilder(ShopChest.this, LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, latestVersion)), LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_CLICK_TO_DOWNLOAD), downloadLink); jb.sendJson(p); } @@ -287,16 +281,10 @@ public class ShopChest extends JavaPlugin { debug("Registering listeners..."); getServer().getPluginManager().registerEvents(new HologramUpdateListener(this), this); - getServer().getPluginManager().registerEvents(new ItemProtectListener(this), this); + getServer().getPluginManager().registerEvents(new ShopItemListener(this), this); getServer().getPluginManager().registerEvents(new ShopInteractListener(this), this); getServer().getPluginManager().registerEvents(new NotifyUpdateOnJoinListener(this), this); getServer().getPluginManager().registerEvents(new ChestProtectListener(this), this); - getServer().getPluginManager().registerEvents(new ItemCustomNameListener(), this); - - if (getServer().getPluginManager().isPluginEnabled("ClearLag")) - getServer().getPluginManager().registerEvents(new ClearLagListener(), this); - - if (lwc) new LWCMagnetListener(this).initializeListener(); } @Override @@ -314,38 +302,6 @@ public class ShopChest extends JavaPlugin { } } - for (World world : Bukkit.getWorlds()) { - for (Entity entity : world.getEntities()) { - if (entity instanceof Item) { - Item item = (Item) entity; - if (item.hasMetadata("shopItem")) { - if (item.isValid()) { - debug("Removing not removed shop item (#" + - (item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")"); - - item.remove(); - } - } - } - } - } - - if (config.enable_debug_log) { - for (World world : Bukkit.getWorlds()) { - for (Entity entity : world.getEntities()) { - if (entity instanceof Item) { - Item item = (Item) entity; - if (item.hasMetadata("shopItem")) { - if (item.isValid()) { - debug("Shop item still valid (#" + - (item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")"); - } - } - } - } - } - } - database.disconnect(); if (fw != null && config.enable_debug_log) { @@ -426,13 +382,6 @@ public class ShopChest extends JavaPlugin { return database; } - /** - * @return Whether LWC is available - */ - public boolean hasLWC() { - return lwc; - } - /** * @return Whether an update is needed (will return false if not checked) */ diff --git a/src/main/java/de/epiceric/shopchest/listeners/ClearLagListener.java b/src/main/java/de/epiceric/shopchest/listeners/ClearLagListener.java deleted file mode 100644 index e46a76a..0000000 --- a/src/main/java/de/epiceric/shopchest/listeners/ClearLagListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.epiceric.shopchest.listeners; - -import me.minebuilders.clearlag.events.EntityRemoveEvent; -import org.bukkit.entity.Entity; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -import java.util.ArrayList; - -public class ClearLagListener implements Listener { - - @EventHandler(priority = EventPriority.HIGH) - public void onEntityRemove(EntityRemoveEvent e) { - ArrayList entityList = new ArrayList<>(e.getEntityList()); - - for (Entity entity : entityList) { - if (entity.hasMetadata("shopItem")) { - e.getEntityList().remove(entity); - } - } - } - - -} diff --git a/src/main/java/de/epiceric/shopchest/listeners/ItemCustomNameListener.java b/src/main/java/de/epiceric/shopchest/listeners/ItemCustomNameListener.java deleted file mode 100644 index f548bfb..0000000 --- a/src/main/java/de/epiceric/shopchest/listeners/ItemCustomNameListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.epiceric.shopchest.listeners; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.ItemSpawnEvent; - -public class ItemCustomNameListener implements Listener { - - @EventHandler(priority = EventPriority.MONITOR) - public void onItemSpawn(ItemSpawnEvent e) { - if (e.getEntity().hasMetadata("shopItem")) { - e.getEntity().setCustomNameVisible(false); - } - } - -} diff --git a/src/main/java/de/epiceric/shopchest/listeners/ItemProtectListener.java b/src/main/java/de/epiceric/shopchest/listeners/ItemProtectListener.java deleted file mode 100644 index 26817ac..0000000 --- a/src/main/java/de/epiceric/shopchest/listeners/ItemProtectListener.java +++ /dev/null @@ -1,134 +0,0 @@ -package de.epiceric.shopchest.listeners; - - -import de.epiceric.shopchest.ShopChest; -import de.epiceric.shopchest.utils.ShopUtils; -import de.epiceric.shopchest.utils.Utils; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.entity.Item; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.*; -import org.bukkit.event.entity.ItemDespawnEvent; -import org.bukkit.event.inventory.InventoryPickupItemEvent; -import org.bukkit.event.player.PlayerBucketEmptyEvent; -import org.bukkit.event.player.PlayerFishEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; - -import java.lang.reflect.InvocationTargetException; - -public class ItemProtectListener implements Listener { - - private ShopUtils shopUtils; - private ShopChest plugin; - - public ItemProtectListener(ShopChest plugin) { - this.plugin = plugin; - this.shopUtils = plugin.getShopUtils(); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onItemDespawn(ItemDespawnEvent e) { - Item item = e.getEntity(); - if (item.hasMetadata("shopItem")) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerPickUpItem(PlayerPickupItemEvent e) { - if (e.getItem().hasMetadata("shopItem")) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onItemPickup(InventoryPickupItemEvent e) { - if (e.getItem().hasMetadata("shopItem")) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onBlockPlace(BlockPlaceEvent e) { - Block b = e.getBlockPlaced(); - Block below = b.getRelative(BlockFace.DOWN); - - if (shopUtils.isShop(below.getLocation())) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onMultiBlockPlace(BlockMultiPlaceEvent e) { - for (BlockState blockState : e.getReplacedBlockStates()) { - Block below = blockState.getBlock().getRelative(BlockFace.DOWN); - if (shopUtils.isShop(below.getLocation())) e.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPistonExtend(BlockPistonExtendEvent e) { - // If the piston would only move itself - Block airAfterPiston = e.getBlock().getRelative(e.getDirection()); - Block belowAir = airAfterPiston.getRelative(BlockFace.DOWN); - if (shopUtils.isShop(belowAir.getLocation())) { - e.setCancelled(true); - return; - } - - for (Block b : e.getBlocks()) { - Block newBlock = b.getRelative(e.getDirection()); - Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN); - if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPistonRetract(BlockPistonRetractEvent e) { - for (Block b : e.getBlocks()) { - Block newBlock = b.getRelative(e.getDirection()); - Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN); - if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void onLiquidFlow(BlockFromToEvent e) { - Block b = e.getToBlock(); - Block below = b.getRelative(BlockFace.DOWN); - - if (shopUtils.isShop(below.getLocation())) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onBucketEmpty(PlayerBucketEmptyEvent e) { - Block b = e.getBlockClicked(); - - if (shopUtils.isShop(b.getLocation())) e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerFish(PlayerFishEvent e) { - if (e.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { - if (e.getCaught() instanceof Item) { - Item item = (Item) e.getCaught(); - if (item.hasMetadata("shopItem")) { - plugin.debug(e.getPlayer().getName() + " tried to fish a shop item"); - e.setCancelled(true); - - // Use some reflection to get the EntityFishingHook class so the hook can be removed... - try { - Class craftFishClass = Class.forName("org.bukkit.craftbukkit." + Utils.getServerVersion() + ".entity.CraftFish"); - Object craftFish = craftFishClass.cast(e.getHook()); - - Class entityFishingHookClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityFishingHook"); - Object entityFishingHook = craftFishClass.getDeclaredMethod("getHandle").invoke(craftFish); - - entityFishingHookClass.getDeclaredMethod("die").invoke(entityFishingHook); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException ex) { - plugin.debug("Failed to kill fishing hook with reflection"); - plugin.debug(ex); - ex.printStackTrace(); - } - } - } - } - } - -} diff --git a/src/main/java/de/epiceric/shopchest/listeners/LWCMagnetListener.java b/src/main/java/de/epiceric/shopchest/listeners/LWCMagnetListener.java deleted file mode 100644 index 7960ef4..0000000 --- a/src/main/java/de/epiceric/shopchest/listeners/LWCMagnetListener.java +++ /dev/null @@ -1,39 +0,0 @@ -package de.epiceric.shopchest.listeners; - -import com.griefcraft.lwc.LWC; -import com.griefcraft.scripting.JavaModule; -import com.griefcraft.scripting.event.LWCMagnetPullEvent; -import de.epiceric.shopchest.ShopChest; - -public class LWCMagnetListener { - - private ShopChest plugin; - - public LWCMagnetListener(ShopChest plugin) { - this.plugin = plugin; - } - - public void initializeListener() { - try { - Class.forName("com.griefcraft.scripting.event.LWCMagnetPullEvent"); - - LWC.getInstance().getModuleLoader().registerModule(ShopChest.getInstance(), new JavaModule() { - - @Override - public void onMagnetPull(LWCMagnetPullEvent event) { - if (event.getItem().hasMetadata("shopItem")) { - event.setCancelled(true); - } - } - - }); - - } catch (ClassNotFoundException ex) { - plugin.debug("Using not recommended version of LWC"); - plugin.getLogger().warning("Shop items can be sucked up by the magnet flag of a protected chest of LWC."); - plugin.getLogger().warning("Use 'LWC Unofficial - Entity locking' v1.7.3 or later by 'Me_Goes_RAWR' to prevent this."); - } - } - - -} diff --git a/src/main/java/de/epiceric/shopchest/listeners/NotifyUpdateOnJoinListener.java b/src/main/java/de/epiceric/shopchest/listeners/NotifyUpdateOnJoinListener.java index a17770f..2dda50e 100644 --- a/src/main/java/de/epiceric/shopchest/listeners/NotifyUpdateOnJoinListener.java +++ b/src/main/java/de/epiceric/shopchest/listeners/NotifyUpdateOnJoinListener.java @@ -27,7 +27,7 @@ public class NotifyUpdateOnJoinListener implements Listener { Player p = e.getPlayer(); if (plugin.isUpdateNeeded()) { - if (p.isOp() || perm.has(p, "shopchest.notification.update")) { + if (perm.has(p, "shopchest.notification.update")) { JsonBuilder jb = new JsonBuilder(plugin, LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, plugin.getLatestVersion())), LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_CLICK_TO_DOWNLOAD), plugin.getDownloadLink()); jb.sendJson(p); } diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java new file mode 100644 index 0000000..6977518 --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java @@ -0,0 +1,126 @@ +package de.epiceric.shopchest.listeners; + +import de.epiceric.shopchest.ShopChest; +import de.epiceric.shopchest.shop.Shop; +import de.epiceric.shopchest.utils.ShopUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.*; +import org.bukkit.event.player.*; + +import java.lang.reflect.InvocationTargetException; + +public class ShopItemListener implements Listener { + + private ShopUtils shopUtils; + private ShopChest plugin; + + public ShopItemListener(ShopChest plugin) { + this.plugin = plugin; + this.shopUtils = plugin.getShopUtils(); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + for (Shop shop : plugin.getShopUtils().getShops()) { + shop.getItem().setVisible(e.getPlayer(), true); + } + } + + @EventHandler + public void onPlayerLeave(PlayerQuitEvent e) { + for (Shop shop : plugin.getShopUtils().getShops()) { + shop.getItem().setVisible(e.getPlayer(), false); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onBlockPlace(BlockPlaceEvent e) { + Block b = e.getBlockPlaced(); + Block below = b.getRelative(BlockFace.DOWN); + + if (shopUtils.isShop(below.getLocation())) { + shopUtils.getShop(below.getLocation()).getItem().resetForPlayer(e.getPlayer()); + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onMultiBlockPlace(BlockMultiPlaceEvent e) { + for (BlockState blockState : e.getReplacedBlockStates()) { + Block below = blockState.getBlock().getRelative(BlockFace.DOWN); + + if (shopUtils.isShop(below.getLocation())) { + shopUtils.getShop(below.getLocation()).getItem().resetForPlayer(e.getPlayer()); + e.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onPistonExtend(BlockPistonExtendEvent e) { + // If the piston would only move itself + Block airAfterPiston = e.getBlock().getRelative(e.getDirection()); + Block belowAir = airAfterPiston.getRelative(BlockFace.DOWN); + if (shopUtils.isShop(belowAir.getLocation())) { + e.setCancelled(true); + return; + } + + for (Block b : e.getBlocks()) { + Block newBlock = b.getRelative(e.getDirection()); + Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN); + if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onPistonRetract(BlockPistonRetractEvent e) { + for (Block b : e.getBlocks()) { + Block newBlock = b.getRelative(e.getDirection()); + Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN); + if (shopUtils.isShop(belowNewBlock.getLocation())) { + e.setCancelled(true); + for (Player p : Bukkit.getOnlinePlayers()) { + shopUtils.getShop(belowNewBlock.getLocation()).getItem().resetForPlayer(p); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onLiquidFlow(BlockFromToEvent e) { + Block b = e.getToBlock(); + Block below = b.getRelative(BlockFace.DOWN); + + if (shopUtils.isShop(below.getLocation())) e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onBucketEmpty(PlayerBucketEmptyEvent e) { + Block clicked = e.getBlockClicked(); + Block underWater = clicked.getRelative(BlockFace.DOWN).getRelative(e.getBlockFace()); + + if (shopUtils.isShop(clicked.getLocation())) { + if (e.getBucket() == Material.LAVA_BUCKET) { + shopUtils.getShop(clicked.getLocation()).getItem().resetForPlayer(e.getPlayer()); + } + } else if (shopUtils.isShop(underWater.getLocation())) { + if (e.getBucket() == Material.LAVA_BUCKET) { + shopUtils.getShop(underWater.getLocation()).getItem().resetForPlayer(e.getPlayer()); + } + } else { + return; + } + + e.setCancelled(true); + } + +} diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java index 1ef2858..e36d31f 100644 --- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java +++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java @@ -13,7 +13,6 @@ import java.util.List; public class Hologram { - private Class entityArmorStandClass; private boolean exists = false; private int count; private List entityList = new ArrayList<>(); @@ -22,18 +21,27 @@ public class Hologram { private List visible = new ArrayList<>(); private ShopChest plugin; + private Class entityArmorStandClass = Utils.getNMSClass("EntityArmorStand"); + private Class nmsWorldClass = Utils.getNMSClass("World"); + private Class packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving"); + private Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); + private Class entityLivingClass = Utils.getNMSClass("EntityLiving"); + public Hologram(ShopChest plugin, String[] text, Location location) { this.plugin = plugin; this.text = text; this.location = location; - try { - entityArmorStandClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityArmorStand"); - } catch (ClassNotFoundException e) { - plugin.debug("Could not find EntityArmorStand class"); - plugin.debug(e); - e.printStackTrace(); - return; + Class[] requiredClasses = new Class[] { + nmsWorldClass, entityArmorStandClass, entityLivingClass, + packetPlayOutSpawnEntityLivingClass, packetPlayOutEntityDestroyClass, + }; + + for (Class c : requiredClasses) { + if (c == null) { + plugin.debug("Failed to create hologram: Could not find all required classes"); + return; + } } create(); @@ -42,8 +50,6 @@ public class Hologram { private void create() { for (String text : this.text) { try { - Class nmsWorldClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".World"); - Object craftWorld = this.location.getWorld().getClass().cast(this.location.getWorld()); Object nmsWorld = nmsWorldClass.cast(craftWorld.getClass().getMethod("getHandle").invoke(craftWorld)); @@ -63,7 +69,7 @@ public class Hologram { entityList.add(entityArmorStand); this.location.subtract(0, 0.25, 0); count++; - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { plugin.debug("Could not create Hologram with reflection"); plugin.debug(e); e.printStackTrace(); @@ -93,14 +99,11 @@ public class Hologram { public void showPlayer(Player p) { for (Object o : entityList) { try { - Class packetClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".PacketPlayOutSpawnEntityLiving"); - Class entityLivingClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityLiving"); - Object entityLiving = entityLivingClass.cast(o); - Object packet = packetClass.getConstructor(entityLivingClass).newInstance(entityLiving); + Object packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving); Utils.sendPacket(plugin, packet, p); - } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException | ClassNotFoundException e) { + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { plugin.debug("Could not show Hologram to player with reflection"); plugin.debug(e); e.printStackTrace(); @@ -115,14 +118,12 @@ public class Hologram { public void hidePlayer(Player p) { for (Object o : entityList) { try { - Class packetClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".PacketPlayOutEntityDestroy"); - int id = (int) entityArmorStandClass.getMethod("getId").invoke(o); - Object packet = packetClass.getConstructor(int[].class).newInstance((Object) new int[] {id}); + Object packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[] {id}); Utils.sendPacket(plugin, packet, p); - } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException | ClassNotFoundException e) { + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { plugin.debug("Could not hide Hologram from player with reflection"); plugin.debug(e); e.printStackTrace(); diff --git a/src/main/java/de/epiceric/shopchest/nms/JsonBuilder.java b/src/main/java/de/epiceric/shopchest/nms/JsonBuilder.java index 752d37e..cb8a5c2 100644 --- a/src/main/java/de/epiceric/shopchest/nms/JsonBuilder.java +++ b/src/main/java/de/epiceric/shopchest/nms/JsonBuilder.java @@ -15,9 +15,30 @@ public class JsonBuilder { private List extras = new ArrayList<>(); private ShopChest plugin; + private Class iChatBaseComponentClass = Utils.getNMSClass("IChatBaseComponent"); + private Class packetPlayOutChatClass = Utils.getNMSClass("PacketPlayOutChat"); + private Class chatSerializerClass; + public JsonBuilder(ShopChest plugin, String text, String hoverText, String downloadLink) { this.plugin = plugin; + if (Utils.getServerVersion().equals("v1_8_R1")) { + chatSerializerClass = Utils.getNMSClass("ChatSerializer"); + } else { + chatSerializerClass = Utils.getNMSClass("ChatBaseComponent$ChatSerializer"); + } + + Class[] requiredClasses = new Class[] { + iChatBaseComponentClass, packetPlayOutChatClass, chatSerializerClass + }; + + for (Class c : requiredClasses) { + if (c == null) { + plugin.debug("Failed to instantiate JsonBuilder: Could not find all required classes"); + return; + } + } + parse(text, hoverText, downloadLink); } @@ -90,24 +111,12 @@ public class JsonBuilder { public void sendJson(Player p) { try { - String version = Utils.getServerVersion(); - - Class iChatBaseComponentClass = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent"); - Class packetPlayOutChatClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutChat"); - Class chatSerializerClass; - - if (version.equals("v1_8_R1")) { - chatSerializerClass = Class.forName("net.minecraft.server." + version + ".ChatSerializer"); - } else { - chatSerializerClass = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer"); - } - Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, toString()); Object packetPlayOutChat = packetPlayOutChatClass.getConstructor(iChatBaseComponentClass).newInstance(iChatBaseComponent); Utils.sendPacket(plugin, packetPlayOutChat, p); } catch (InstantiationException | InvocationTargetException | - IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { + IllegalAccessException | NoSuchMethodException e) { plugin.debug("Failed to send packet with reflection"); plugin.debug(e); e.printStackTrace(); diff --git a/src/main/java/de/epiceric/shopchest/nms/SpawnEggMeta.java b/src/main/java/de/epiceric/shopchest/nms/SpawnEggMeta.java index 521dd57..8e3a649 100644 --- a/src/main/java/de/epiceric/shopchest/nms/SpawnEggMeta.java +++ b/src/main/java/de/epiceric/shopchest/nms/SpawnEggMeta.java @@ -11,7 +11,12 @@ public class SpawnEggMeta { private static String getNBTEntityID(ShopChest plugin, ItemStack stack) { try { - Class craftItemStackClass = Class.forName("org.bukkit.craftbukkit." + Utils.getServerVersion() + ".inventory.CraftItemStack"); + Class craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack"); + + if (craftItemStackClass == null) { + plugin.debug("Failed to get NBTEntityID: Could not find CraftItemStack class"); + return null; + } Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, stack); @@ -24,7 +29,7 @@ public class SpawnEggMeta { Object id = entityTagCompound.getClass().getMethod("getString", String.class).invoke(entityTagCompound, "id"); if (id instanceof String) return (String) id; - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { plugin.debug("Could not get NBTEntityID with reflection"); plugin.debug(e); e.printStackTrace(); diff --git a/src/main/java/de/epiceric/shopchest/shop/Shop.java b/src/main/java/de/epiceric/shopchest/shop/Shop.java index 410e815..f2e4a23 100644 --- a/src/main/java/de/epiceric/shopchest/shop/Shop.java +++ b/src/main/java/de/epiceric/shopchest/shop/Shop.java @@ -5,7 +5,6 @@ import de.epiceric.shopchest.config.Regex; import de.epiceric.shopchest.language.LanguageUtils; import de.epiceric.shopchest.language.LocalizedMessage; import de.epiceric.shopchest.nms.Hologram; -import de.epiceric.shopchest.utils.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -14,15 +13,9 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Chest; import org.bukkit.block.DoubleChest; -import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.util.Vector; - -import java.util.UUID; public class Shop { @@ -32,7 +25,7 @@ public class Shop { private ItemStack product; private Location location; private Hologram hologram; - private Item item; + private ShopItem item; private double buyPrice; private double sellPrice; private ShopType shopType; @@ -71,7 +64,7 @@ public class Shop { } if (hologram == null || !hologram.exists()) createHologram(); - if (item == null || item.isDead()) createItem(); + if (item == null) createItem(); } private Shop(OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) { @@ -117,25 +110,18 @@ public class Shop { if (plugin.getShopChestConfig().show_shop_items) { plugin.debug("Creating item (#" + id + ")"); - Item item; Location itemLocation; ItemStack itemStack; - ItemMeta itemMeta = product.getItemMeta(); - itemMeta.setDisplayName(UUID.randomUUID().toString()); itemLocation = new Location(location.getWorld(), hologram.getLocation().getX(), location.getY() + 1, hologram.getLocation().getZ()); - itemStack = new ItemStack(product); + itemStack = product.clone(); itemStack.setAmount(1); - itemStack.setItemMeta(itemMeta); - item = location.getWorld().dropItem(itemLocation, itemStack); - item.setVelocity(new Vector(0, 0, 0)); - item.setMetadata("shopItem", new FixedMetadataValue(plugin, true)); - item.setMetadata("shopId", new FixedMetadataValue(plugin, id)); - item.setCustomNameVisible(false); - item.setPickupDelay(Integer.MAX_VALUE); + this.item = new ShopItem(plugin, itemStack, itemLocation); - this.item = item; + for (Player p : Bukkit.getOnlinePlayers()) { + item.setVisible(p, true); + } } } @@ -269,6 +255,13 @@ public class Shop { return hologram; } + /** + * @return Floating {@link ShopItem} of the shop + */ + public ShopItem getItem() { + return item; + } + /** * @return {@link InventoryHolder} of the shop or null if the shop has no chest. */ diff --git a/src/main/java/de/epiceric/shopchest/shop/ShopItem.java b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java new file mode 100644 index 0000000..195976b --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java @@ -0,0 +1,155 @@ +package de.epiceric.shopchest.shop; + +import de.epiceric.shopchest.ShopChest; +import de.epiceric.shopchest.utils.Utils; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +public class ShopItem { + + private ShopChest plugin; + private Map visible = new HashMap<>(); + private ItemStack itemStack; + private Location location; + + private Object entityItem; + private int entityId; + private Object[] creationPackets = new Object[3]; + + private Class nmsWorldClass = Utils.getNMSClass("World"); + private Class craftWorldClass = Utils.getCraftClass("CraftWorld"); + private Class nmsItemStackClass = Utils.getNMSClass("ItemStack"); + private Class craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack"); + private Class entityItemClass = Utils.getNMSClass("EntityItem"); + private Class entityClass = Utils.getNMSClass("Entity"); + private Class packetPlayOutSpawnEntityClass = Utils.getNMSClass("PacketPlayOutSpawnEntity"); + private Class packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata"); + private Class dataWatcherClass = Utils.getNMSClass("DataWatcher"); + private Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); + private Class packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity"); + + public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) { + this.plugin = plugin; + this.itemStack = itemStack; + this.location = location; + + Class[] requiredClasses = new Class[] { + nmsWorldClass, craftWorldClass, nmsItemStackClass, craftItemStackClass, entityItemClass, + packetPlayOutSpawnEntityClass, packetPlayOutEntityMetadataClass, dataWatcherClass, + packetPlayOutEntityDestroyClass, entityClass, packetPlayOutEntityVelocityClass, + }; + + for (Class c : requiredClasses) { + if (c == null) { + plugin.debug("Failed to create shop item: Could not find all required classes"); + return; + } + } + + create(); + } + + private void create() { + try { + Object craftWorld = craftWorldClass.cast(location.getWorld()); + Object nmsWorld = craftWorldClass.getMethod("getHandle").invoke(craftWorld); + + Object nmsItemStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, itemStack); + + Constructor entityItemConstructor = entityItemClass.getConstructor(nmsWorldClass); + entityItem = entityItemConstructor.newInstance(nmsWorld); + + entityItemClass.getMethod("setPosition", double.class, double.class, double.class).invoke(entityItem, location.getX(), location.getY(), location.getZ()); + entityItemClass.getMethod("setItemStack", nmsItemStackClass).invoke(entityItem, nmsItemStack); + + Field ageField = entityItemClass.getDeclaredField("age"); + ageField.setAccessible(true); + ageField.setInt(entityItem, -32768); + + entityId = (int) entityItemClass.getMethod("getId").invoke(entityItem); + Object dataWatcher = entityItemClass.getMethod("getDataWatcher").invoke(entityItem); + + creationPackets[0] = packetPlayOutSpawnEntityClass.getConstructor(entityClass, int.class).newInstance(entityItem, 2); + creationPackets[1] = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(entityId, dataWatcher, true); + creationPackets[2] = packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(entityId, 0D, 0D, 0D); + } catch (NoSuchMethodException | NoSuchFieldException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + plugin.debug("Failed to create shop item with reflection"); + plugin.debug(e); + e.printStackTrace(); + } + } + + public void remove() { + for (Player p : visible.keySet()) { + if (isVisible(p)) setVisible(p, false); + } + } + + /** + * Respawns the item at the set location for a player + * @param p Player, for which the item should be reset + */ + public void resetForPlayer(Player p) { + setVisible(p, false); + setVisible(p, true); + } + + public boolean isVisible(Player p) { + return visible.get(p) == null ? false : visible.get(p); + } + + public void setVisible(final Player p, boolean visible) { + if (this.visible.containsKey(p) && this.visible.get(p) == visible) + return; + + if (visible) { + for (Object packet : this.creationPackets) { + Utils.sendPacket(plugin, packet, p); + } + } else { + try { + if (p.isOnline()) { + Object packetPlayOutEntityDestroy = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId}); + Utils.sendPacket(plugin, packetPlayOutEntityDestroy, p); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { + plugin.debug("Failed to destroy shop item with reflection"); + plugin.debug(e); + e.printStackTrace(); + } + } + + this.visible.put(p, visible); + } + + + /** + * @return Clone of the location, where the shop item should be (it could have been moved by something, even though it shouldn't) + * To get the exact location, use reflection and extract the location of the {@code EntityItem} + * which you can get in {@link #getEntityItem()}. + */ + public Location getLocation() { + return location.clone(); + } + + /** + * @return {@code net.minecraft.server.[VERSION].EntityItem} + */ + public Object getEntityItem() { + return entityItem; + } + + /** + * @return A clone of this Item's {@link ItemStack} + */ + public ItemStack getItemStack() { + return itemStack.clone(); + } +} diff --git a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java index bdef077..37bd4f2 100644 --- a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java +++ b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java @@ -221,36 +221,6 @@ public class ShopUtils { } } - for (World world : Bukkit.getWorlds()) { - for (Entity entity : world.getEntities()) { - if (entity instanceof Item) { - Item item = (Item) entity; - if (item.hasMetadata("shopItem")) { - if (item.isValid()) { - plugin.debug("Removing not removed shop item (#" + - (item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")"); - - item.remove(); - } - } - } - } - } - - for (World world : Bukkit.getWorlds()) { - for (Entity entity : world.getEntities()) { - if (entity instanceof Item) { - Item item = (Item) entity; - if (item.hasMetadata("shopItem")) { - if (item.isValid()) { - plugin.debug("Shop item still valid (#" + - (item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")"); - } - } - } - } - } - int count = 0; for (int id = 1; id <= highestId; id++) { diff --git a/src/main/java/de/epiceric/shopchest/utils/Utils.java b/src/main/java/de/epiceric/shopchest/utils/Utils.java index ca76da1..eb790db 100644 --- a/src/main/java/de/epiceric/shopchest/utils/Utils.java +++ b/src/main/java/de/epiceric/shopchest/utils/Utils.java @@ -53,6 +53,30 @@ public class Utils { return amount; } + /** + * @param className Name of the class + * @return Class in {@code net.minecraft.server.[VERSION]} package with the specified name or {@code null} if the class was not found + */ + public static Class getNMSClass(String className) { + try { + return Class.forName("net.minecraft.server." + getServerVersion() + "." + className); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * @param className Name of the class + * @return Class in {@code org.bukkit.craftbukkit.[VERSION]} package with the specified name or {@code null} if the class was not found + */ + public static Class getCraftClass(String className) { + try { + return Class.forName("org.bukkit.craftbukkit." + getServerVersion() + "." + className); + } catch (ClassNotFoundException e) { + return null; + } + } + /** * Send a packet to a player * @param packet Packet to send